Introduction: An API is Not Just Requests, It's a Dialogue
Once upon a time, in the era of monoliths, developers wrote backends in PHP or Java, and the frontend simply called ready-made URLs. Everything was simple, but slow. Today, the TypeScript ecosystem dictates new rules: we want not just to exchange data, but to do it safely, strictly, and with autocompletion. This is where three giants come into play: REST, GraphQL, and gRPC.
In this article, we won't be retelling the documentation. We'll go through each protocol from a TypeScript practitioner's perspective: we'll see real code examples, compare performance, and understand which tool is suitable for your project. If you write in TypeScript and want to level up your API skills, you've come to the right place.
REST API in TypeScript: A Classic That Never Dies
REST (Representational State Transfer) is the de facto standard. Simplicity, HTTP methods (GET, POST, PUT, DELETE), and resources. But in TypeScript, REST reveals itself in a new way: we get strict typing for requests and responses.
What a Typed REST Client Looks Like
Let's write a minimal but working client for a REST API in TypeScript. No extra libraries — just fetch and types.
// Types for the responseinterface User { id: number; name: string; email: string;}
interface ApiResponse<T> { data: T; status: number; message: string;}
// Typed clientclass RestClient { private baseUrl: string;
constructor(baseUrl: string) { this.baseUrl = baseUrl; }
async get<T>(endpoint: string): Promise<ApiResponse<T>> { const response = await fetch(`${this.baseUrl}${endpoint}`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, });
if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); }
const data: T = await response.json(); return { data, status: response.status, message: 'Success', }; }
async post<T, U>(endpoint: string, body: U): Promise<ApiResponse<T>> { const response = await fetch(`${this.baseUrl}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), });
if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); }
const data: T = await response.json(); return { data, status: response.status, message: 'Created', }; }}
// Usageconst client = new RestClient('https://jsonplaceholder.typicode.com');
async function fetchUser() { const response = await client.get<User>('/users/1'); console.log(response.data.name); // Autocompletion works!}
fetchUser();As you can see, REST API TypeScript gives us control over types at every step. A mistake in a field name? TypeScript will catch it at compile time.
When to Choose REST
- Simplicity — REST is easy to understand and maintain.
- Caching — HTTP caching works out of the box.
- CRUD applications — blogs, stores, admin panels.
The downside: with complex nested queries, you either get a lot of data (over-fetching) or make N requests (N+1 problem).
GraphQL in TypeScript: Flexibility and Precision
GraphQL is a query language created by Facebook. Its main feature: you decide which fields to get. GraphQL TypeScript is a powerful tandem that provides full type safety on both the client and server.
Writing a Typed GraphQL Query
For the example, we'll use the graphql-request library — a lightweight client for TypeScript.
import { request, gql } from 'grap