Suhas Deshpande
July 08, 2021

When I Googled “what is GraphQL” to learn more about the network protocols, all I saw was a comparison between REST and GraphQL. Most of the conclusions said, “use GraphQL.” It felt very binary (and trendy, for that matter), which is a problem because each product and use-case is unique. The fact is, whatever is newest and shiniest gets recommended more loudly. But you have to weigh the trade-offs and come up with a solution that is best for your situation.
There is a general understanding that either REST is better than GraphQL or vice versa. But the truth is they both address different problems and have different strengths and weaknesses. The question isn’t necessarily which one is better to use, but which one is better to use for specific circumstances. The best way to evaluate GraphQL, REST, or any other technology is to figure out your constraints based on the problem you are going to solve.
REST is the popular option, and for good reason: it’s quick to learn and implement. No one wants to write complex code if they don’t have to, so it’s not hard to see why quick and easy REST beats complex and clever GraphQL in most situations.
Two aspects of REST really shine. The first is that most ORMs (object–relational mapping) are optimized to work with REST, so that is not a problem you will have to solve. The second is REST is ideal for quick transactional requests, like the following:
Additionally, with REST, you share the complexity between client and server. It has the benefit of proven patterns on how to do something, and it is definitely a well-proven, time-tested way of writing applications.
GraphQL is a different approach entirely, and it takes time and effort to create harmony. A system that is ideal for all your use cases will demand a lot of your time and will require your team to learn a lot of new concepts. It will force you to think in a certain way. We knew the cost of implementing GraphQL was probably going to be exhaustive — we expected the move was not going to be a do-it-once-and-forget-it thing.
The default option would be REST, and it’s a good one. Not just because it’s easy, but because there is an expectation of shared familiarity. Developers are likely to already know REST, whereas the same is not true for GraphQL. Unless you’ve identified needs that GraphQL is especially good at solving, REST is the default choice for a good reason. We would rather rest easy with REST, build something that both excites and helps our users, and get their product in the hands of their friends and family with time-tested methods.
Let’s get right into it. With REST, there’s a lot of back and forth and manual work. Calls can result in either over-fetching or under-fetching based on the API contract. For example, if you end up with URIs (uniform resource identifiers) and not the specific data you’re looking for, the network calls you need to make on your client to get what you need escalate quickly.
Whereas GraphQL gets exactly the data you want on an API call. You have control over the query on a granular level, which is not something you can easily do with REST since it’s not made for that specific purpose. Having this granular control will allow you to have fewer network calls and will require fewer developmental changes on client applications, since responsibility has been shifted to backends.
Applications can be informationally and visually heavy and also need to retrieve a variety of interconnected data. Fewer network calls in this scenario can be crucial for performance when this data needs to be retrieved in what are potentially many different and unknown situations. With the right implementation of GraphQL, you can eliminate the ability to over-fetch.
If your application is reading 1MB of data locally, it can finish the job in 0.4 milliseconds — as opposed to reading it over the network, which can take 150 milliseconds — a drastic difference. The point here is you want to optimize for fewer network requests. REST doesn’t give you those affordances. If optimizing for network requests is the most important factor, GraphQL would work better for you.
With GraphQL, you’ll get only the information you need. This is due to a self-documenting schema that’s easier to consume and has better tooling around consuming your endpoint.
Writing GraphQL queries is just the tip of the iceberg. Implementing a brand new solution with GraphQL comes with challenges to overcome and problems to solve. The GraphQL stack might appear to be simple to get started with, but it gets complex quickly. It requires a lot of upfront learning and can be intimidating for newcomers to figure out how all these pieces fit together. Before you move to GraphQL, you need to understand what you’re signing up for.
Since GraphQL is not built into an ORM, it means there’s a lot of architectural choices that need to be made for your stack that will require extra effort to set up. It also comes with a steep learning curve. Part of that learning curve is control. GraphQL inverts control and hands it over to the client. Any amount of information can be requested, which means building a third-party API can become burdensome if you have expensive queries.
We all know naming things is hard, and GraphQL makes this a little harder. The GraphQL schema is a list of object types. It’s more than just a catalog; it’s a snapshot of the API. In a very large codebase, this could lead to naming collisions, so thoughtful naming conventions are a must.
In addition to the learning curves we’ve found ourselves dealing with, caching, one of the most loved and hated topics in computer science, is a whole lot more adventurous with GraphQL. Caching can happen on the client or on the server. REST uses headers to control the caching. However, GraphQL doesn’t have this affordance at the HTTP level.
All POST requests in GraphQL automatically opted out of the free HTTP caching you get in the browser. That means you have to bring your own cache when you’re in GraphQL land. Not only is this caching solution something you have to build, but you also have to maintain it — a consideration that should be thought about thoroughly.
In the end, you want to build something useful. GraphQL and REST can get you there, but be mindful about what either option means for you and your team long term. We identified that GraphQL was a great solution for some of our needs.
The general wisdom is to choose either REST or GraphQL for your needs, but in reality, neither one covers all the bases. Our biggest motivation when building something is to evaluate how a developer would like using it. We started gradually introducing GraphQL first in our own internal APIs. We converted those endpoints to see how they worked for us internally before we exposed them externally.
But for Courier’s studio application, we went with GraphQL. Even though REST would allow us to build it, it wasn’t necessarily the best solution. It would require us to either implement (or add more) logic to handle new information and endpoints every time we decide to have new info on a page. Because the application has a growing need for data and information, and GraphQL solves this problem better for us.
Our backend data objects and business objects are rapidly becoming more interconnected, and GraphQL is a good way to relate each business object within our ecosystem. Managing all that in REST would be quite a task. And it comes with the downside of additional network requests.
GraphQL solved our constraints fairly well. So while conventional wisdom states we should move to GraphQL entirely, the fact is, for customer-facing APIs, we need REST. For our own frontends, however, GraphQL is the answer.
We decided to tackle the learning curve even though learning has uncertain pay-offs. Everything we do has opportunity cost, and early adoption of something like GraphQL can feel like cargo cult-like behavior. But in the end, GraphQL works for us, with our circumstances.
Courier frontend is growing rapidly with the need to present rich information coming from various sources. REST could work, but GraphQL would work even better because of the query-able, type-safe nature of GraphQL.
Let’s take a look:
Copied!
type Notification implements Node {archived: BooleanbrandEnabled: BooleanbrandId: StringcategoryId: Stringcreated: DateTime! @iso8601draftId: Stringdraft: TemplateDrafteventMaps: EventMapConnectionid: ID!name: String!tagIds: [String]templateId: String!updated: DateTime @iso8601}
Here is a type defined in our GraphQL. We know in the future this Notification node will have more information needed. Say, a use-case to present all the collaborators involved in building this notification. With GraphQL (based on our existing pattern), we can easily extend the Notification node and add an edge or connect it to User (collaborator), so this extensibility works out great for our use case.
Another big advantage of GraphQL is the fact we can choose what data to get from our backend. Let’s steal this analogy from Dan Abramov where he explains the new React 18 batch rendering feature. Let’s say you need to go shopping for breakfast items at the store. Not only would you ideally like to come up with a reasonable list of items before going to the store, but you’d also want to optimize the number of trips you need to take.
The grocery scenario is quite similar to client applications needing to make additional network calls to the backend, like every time there’s a need for additional fields. GraphQL solves this by providing a declarative, type-safe mechanism to query data from your backend.
It’s fairly easy to look at the query below and figure out what information you can get.
Copied!
query {user {role {labelkey}signatureworkspace {createdidnameusage}userIdfirstNamelastActive}}
And tooling like introspection allows us to go even further! Introspection lets you gather information about supported queries in the GraphQL schema — you can get autocomplete schema definitions with type information. Beyond that, strong-typing makes introspection easier. This can be accomplished using TypeScript interfaces and is what will allow you to specify what your data is as well as automate a lot of the documentation.
This declarative approach to query information resonated with us about why GraphQL is compelling to use. So we chose GraphQL for our client applications because it provides strong typing and structured queries. GraphQL has a specification that makes it intuitive to build applications.
We haven’t given up REST altogether, though — we’re still using REST for our external APIs. This is because they’re transactional in nature, and it isn’t advantageous for us to expose an external GraphQL endpoint just yet based on the simplicity of our REST endpoints.
These are some of the reasons we love GraphQL at Courier.
Since we are a developer API product, it’s expected that we support all formats, REST obviously included. REST is widely used, popular, easy to maintain, and has an easy triage way of exposing endpoints. We know how and what to measure in our REST endpoints. We have pretty decent tooling built around the REST ecosystem, and moving it to GraphQL won’t add much value to the user or the business right away.
But GraphQL works best for us moving forward with our client applications. Exposing Courier’s functionality to external developers, we will still be resting on REST. And we still have a decent amount (if not most) of Courier’s functionality exposed via REST endpoints.
We have been running GraphQL on NodeJS for more than one year at this point. It has increased our velocity to push new features as well as given us a significant performance boost in our Studio application. We get the advantage of the high bandwidth on the server side to pull data from GraphQL’s middle layer, which is responsible for stitching data from various data sources.
GraphQL is an exciting field — with a learning curve! But once you get the hang of it, specifications provide great tooling out of the box and saves a lot of code. Finding the balance between getting features out the door and adopting new technology can be a challenge. Finding the iterative approach that gives you time to adopt new technology would help you gradually increase your chances of success.
So there is no silver bullet that solves all the problems. New solutions are inspired by shortcomings in existing technology, but they come with new sets of challenges. Everyone’s situation is different, and contextualizing the solutions based on your problem space is the way to think when making these fun decisions.
Torn between REST and GraphQL? Chat with a product expert to learn how Courier found the balance between getting features out the door and adopting new technology.

Expo Push Notifications: The Complete Implementation Guide (SDK 52+)
Expo push notifications are alerts sent from a server to a user's phone, even when the app isn't open. To set them up, install the expo-notifications library, ask the user for permission, and get a unique push token for their device. Your server sends a message to Expo's push service with that token, and Expo delivers it through Apple or Google. Push notifications only work on real phones, not simulators. Local notifications are different — they're scheduled by the app itself for things like reminders. You can also route Expo push through services like Courier to add email, SMS, and Slack fallbacks.
By Kyle Seyler
February 24, 2026

Best Email API Providers for Developers in 2026: SendGrid vs Postmark vs Mailgun vs SES vs Resend
Your email provider sticks with you longer than most technical decisions. Courier handles notification infrastructure for thousands of teams, so we went deep on the six email providers that show up most: SendGrid, Postmark, Mailgun, Amazon SES, Resend, and SMTP. This guide covers real API primitives, actual code from each provider's docs, Courier integration examples with provider overrides, and an honest read on where each developer experience holds up and where it breaks down. We also asked Claude to review every API and tell us which one it would wire up first. The answer surprised us.
By Kyle Seyler
February 23, 2026

The Courier MCP Server Is Open Source. Here's How It Actually Works.
Courier's MCP server is open source at github.com/trycourier/courier-mcp. It connects AI coding tools like Cursor and Claude Code to your Courier account so they can send messages, manage users, and install SDKs without hallucinating API details. This post walks through the actual codebase: how 16 tool classes are registered (and how a config allowlist gates most of them), why we pull installation guides from GitHub at runtime instead of bundling them, how the DocsTools class generates live JWTs alongside setup instructions, and what the SdkContextTools class does in the repo to prevent v7/v8 SDK conflicts (even though it isn't wired into the server yet).
By Mike Miller
February 06, 2026
© 2026 Courier. All rights reserved.