For a product with both internal and external APIs consumed by web and mobile clients, I would optimize for three things:
- Stable public contracts.
- Low operational complexity.
- A developer experience that doesn't become a maintenance burden in 3–5 years.
In practice, that usually leads to one of these architectures:
| Situation | Recommendation |
|---|
| Public API used by partners, third parties, SDKs | REST |
| Internal frontend ↔ backend for a single product | GraphQL or tRPC |
| Full TypeScript stack owned by one team | tRPC |
| Mixed ecosystem (mobile, web, external developers) | REST + GraphQL (or REST internally) |
REST
Pros
- Extremely stable over time.
- Every language and platform supports it.
- Great tooling (OpenAPI, SDK generation, API gateways, caching).
- Easier for external developers.
- Easier to version.
Example:
GET /users/42
GET /users/42/orders
POST /orders
Long-term maintainability
Very good.
REST changes slowly because endpoints become contracts.
You can version:
/v1/orders
/v2/orders
or evolve fields without breaking clients.
Large companies (Stripe, GitHub, Twilio, AWS) all expose REST because external compatibility matters.
Downsides
Frontend sometimes needs multiple requests.
User
Orders
Notifications
may require three round trips.
GraphQL
GraphQL shines when the frontend needs flexibility.
Instead of multiple endpoints:
GET /user
GET /orders
GET /notifications
one query:
query {
user {
name
orders {
total
}
notifications {
title
}
}
}
Pros
- Clients fetch exactly what they need.
- Excellent for mobile.
- Eliminates over-fetching.
- Single endpoint.
- Strong schema.
Long-term maintainability
Generally good—but more operationally complex.
You have to maintain:
- schema evolution
- resolver performance
- N+1 problems
- query cost limiting
- caching strategy
- persisted queries
- security around arbitrary queries
As teams grow, GraphQL often requires dedicated ownership.
tRPC
tRPC is essentially:
TypeScript functions exposed over HTTP.
Example:
const user = await api.users.getById.query(id)
No OpenAPI.
No GraphQL schema.
No duplicated DTOs.
Types flow directly.
Pros
Amazing developer experience.
- autocomplete
- end-to-end typing
- almost zero boilerplate
- refactoring is much easier
For an all-TypeScript stack it's hard to beat.
Long-term maintainability
Excellent inside one organization.
Less ideal for public APIs.
Problems appear when:
- Swift app
- Kotlin app
- Python service
- Go service
- partners
need to consume it.
tRPC is fundamentally optimized around the TypeScript ecosystem.
Mobile considerations
If you're shipping iOS and Android:
REST:
GET /products
GET /cart
GET /profile
works everywhere.
GraphQL can reduce network usage significantly because mobile apps can request only needed fields.
tRPC works well if you're using React Native with TypeScript.
Native Swift/Kotlin clients lose much of its biggest advantage.
External API considerations
If customers integrate your API:
REST wins.
Reasons:
- curl examples
- Postman
- OpenAPI
- SDK generation
- language neutrality
- easier documentation
GraphQL public APIs exist (GitHub is a famous example), but they require more sophistication from consumers.
tRPC is rarely chosen for public APIs.
Internal API considerations
For internal dashboards or a React application:
tRPC is outstanding.
It removes an enormous amount of repetitive work.
GraphQL is also good if multiple frontend teams have different data needs.
Team size matters
2–10 engineers
I'd lean toward:
- REST
- or tRPC if full TypeScript
Simple beats clever.
20–100 engineers
GraphQL starts paying off when:
- many frontend teams
- many shared resources
- multiple clients needing different data shapes
Hundreds of engineers
Often you'll see:
- REST for public APIs
- GraphQL gateway for product UI
- internal gRPC between services
My recommendation for your case
You mentioned:
- internal API
- external API
- web
- mobile
I would avoid making one technology solve every problem.
A pragmatic architecture is:
Mobile
|
Web App
|
-------------------
GraphQL or REST BFF
-------------------
|
Internal services
|
REST/gRPC
And expose a separate public REST API for third parties.
Alternatively, if your web app is entirely TypeScript and you're not supporting native mobile clients directly, you could use:
- tRPC internally for your React frontend.
- REST externally for partners and public integrations.
That gives you excellent developer productivity without locking external consumers into your implementation choices.
If I were starting a new product today
- Public API: REST with OpenAPI documentation.
- Internal React frontend: tRPC if the backend is also TypeScript; otherwise REST.
- GraphQL: Adopt it only if you have clear needs for flexible client-driven queries or multiple frontend teams that would substantially benefit from a shared schema.
That combination tends to provide the best long-term maintainability: stable public contracts, simple operations, and a productive developer experience for the product team.