Every few months someone posts a “GraphQL killed REST” or “gRPC is the future” article and the comments fill up with strong opinions. Having built APIs in all three styles across various projects – some small SaaS products, some internal tooling, one mobile app backend – I’ve landed on a fairly boring conclusion: REST is the right default for small projects, and you should deviate from it only when you have a concrete reason to.
Here’s how I actually think about the tradeoffs.
REST: boring in the best way
REST over HTTP/JSON is what every developer already knows, every client library supports, and every proxy, gateway, and monitoring tool understands. It has no required build step, no schema to compile, and curl is all you need to debug it.
For a small project – a side project, a simple internal API, a startup’s first backend – the main thing that kills you is complexity you didn’t need. REST keeps the operational surface small. You get standard HTTP status codes, cacheable GET responses, and a mental model that maps cleanly to database resources.
The criticism usually leveled at REST for small projects is over- and under-fetching: you get more data than you need, or you need multiple requests to build a page. Both are real, but they’re usually not a problem at small scale. If your API has ten endpoints and the biggest response is a few kilobytes, optimizing for network efficiency is premature. Build the thing first.
REST is also the de facto standard for public APIs, which matters more than people admit. If you ever want to document and publish your API, REST with OpenAPI is the path that third-party developers expect. Real-world payment API integration is a good example of this in practice – public financial APIs almost universally use REST because the tooling ecosystem around authentication, versioning, and documentation is mature and widely understood.
GraphQL: useful when your clients have different data needs
GraphQL solves a specific problem: multiple clients (say, a mobile app and a web dashboard) that need different shapes of the same data. Instead of versioning your API or building bespoke endpoints, you expose a single typed schema and let clients ask for exactly what they need.
That’s genuinely useful. But it comes with real costs for a small team:
- You need a schema definition and a resolver layer, which is more upfront work than REST routes
- Caching is harder because everything goes to
POST /graphqlby default – you lose HTTP caching semantics - N+1 query problems appear quickly and require DataLoader or similar batching patterns to fix
- Tooling like API gateways and monitoring tools need GraphQL-aware configuration
I’ve used GraphQL successfully on a project with a React web app and a React Native mobile app sharing a backend. Having one schema that both clients query made sense. For a project with a single client, I’d think hard about whether I’m buying complexity I don’t need. See graphql.org/learn for the official introduction if you want to evaluate it properly.
gRPC: the right tool for a specific job
gRPC uses Protocol Buffers over HTTP/2 and is optimized for high-throughput, low-latency communication between services. It’s excellent for internal microservice communication where you control both ends of the connection and you care about performance.
For small projects, the overhead is usually not worth it. You need to:
- Define your service in a
.protofile - Run the protoc compiler to generate client and server stubs in your language
- Keep the generated code in sync across services as the schema evolves
- Handle the fact that browsers can’t use gRPC directly (you need grpc-web or a proxy layer)
That’s a meaningful toolchain to maintain. The payoff – binary encoding, multiplexing, bidirectional streaming, generated typed clients – is real, but you need to be at a scale where it matters. grpc.io describes the use cases well. The short version: gRPC shines when you’re building internal service-to-service communication at scale, not when you’re building a CRUD API for a web app with two hundred users.
What about tRPC?
If you’re working in a TypeScript-first stack where the client and server are in the same repo, tRPC is worth a look. It gives you end-to-end type safety without a schema compilation step – you define procedures on the server and they’re automatically typed on the client. The constraint is that it only works when both sides are TypeScript, and it’s not a great fit for public APIs that third-party developers will consume.
I mention it because it addresses the “I want GraphQL-style type safety without the overhead” desire that often shows up in small TypeScript projects. It’s not a replacement for REST or GraphQL in all cases, but if your specific situation is a Next.js app with its own API layer, it can eliminate a lot of boilerplate.
A decision tree that actually fits on one screen
Start with REST. Move to GraphQL if you genuinely have multiple clients with divergent data needs and you’ve felt the pain of over-fetching or endpoint proliferation. Move to gRPC if you’re building internal microservices and have profiled a real latency or throughput bottleneck. Consider tRPC if you’re in a TypeScript monorepo and want type-safe API calls without generated code.
“We might need GraphQL later” is not a reason to use GraphQL now. Migrations between API styles are annoying, but they’re not impossible, and building the right thing for your current scale is usually better than building for a scale you haven’t reached. REST has carried projects from prototype to millions of users plenty of times. The familiarity of the format means fewer surprises, faster onboarding for collaborators, and a shorter path to documentation and third-party integrations. For small projects, that matters.