GraphQL vs REST in TypeScript: A Clash of Titans or a Choice Based on the Task?
\n\nWhen you write in TypeScript, the question of backend architecture becomes especially acute. You want not just "for it to work," but for types to fly, errors to be caught at compile time, and code to be predictable. And here the developer faces the eternal choice: the time-tested REST or the trendy, flexible GraphQL? Both technologies work great with TypeScript, but each dictates its own development style.
\n\nIn this article, we won't beat around the bush or state the obvious. We will break down GraphQL vs REST in TypeScript with specific examples, code, bottlenecks, and scenarios where one approach outperforms the other. Let's go!
\n\nREST: An Old Friend That Doesn't Let You Down
\n\nREST (Representational State Transfer) is an architectural style where each resource (user, post, comment) has its own URL. You hit /api/users/1 and get the user. Simple and predictable. In TypeScript projects, REST is often implemented via Express, Fastify, or NestJS.
Typing in REST with TypeScript
\nThe main pain point of REST is the weak link between the backend and frontend. You send JSON, and the frontend guesses: "What came back?" TypeScript saves the day through shared types or OpenAPI (Swagger).
\n\nExample: Backend on Express with TypeScript
\n\nimport express, { Request, Response } from 'express';\n\ninterface User {\n id: number;\n name: string;\n email: string;\n}\n\nconst app = express();\napp.get('/api/users/:id', (req: Request, res: Response) => {\n const user: User = { id: +req.params.id, name: 'Alice', email: 'alice@example.com' };\n res.json(user);\n});\n\n\nFrontend (fetch):
\n\ninterface User {\n id: number;\n name: string;\n email: string;\n}\n\nasync function getUser(id: number): Promise<User> {\n const res = await fetch(`/api/users/${id}`);\n return res.json() as Promise<User>;\n}\n\n\nProblem: if the backend changes the User structure, the frontend will only find out at runtime. The types on the frontend are just a copy. Solution: generate types from the OpenAPI specification.
\n\nPros of REST in TypeScript
\n- \n
- Simplicity of caching: HTTP cache (CDN, browser) works out of the box. \n
- Clear structure: URL = resource, methods = actions (GET, POST, PUT, DELETE). \n
- Wide support: Any client (browser, curl, Postman) understands REST. \n
- Low entry barrier: Even a junior can figure it out in an hour. \n
Cons of REST
\n- \n
- Over-fetching and Under-fetching: You either pull extra fields or make multiple requests. \n
- Rigid structure: To get a user + their posts, you need two requests or workarounds (expand field). \n
- Weak typing: Without OpenAPI, types on the client are just a "promise." \n
GraphQL: Flexibility and Control
\n\nGraphQL is a query language that gives the client the right to decide for itself what data to receive. One endpoint (/graphql), one request — and you get exactly what you ordered. TypeScript and GraphQL are a perfect pair: types are automatically generated from the schema.
Typing in GraphQL with TypeScript
\nFirst, the schema (SDL) is written:
\n\ntype User {\n id: ID!\n name: String!\n email: String!\n posts: [Post!]!\n}\n\ntype Post {\n id: ID!\n title: String!\n content: String!\n}\n\ntype Query {\n user(id: ID!): User\n}\n\n\nThen types are generated (via graphql-codegen):
// generated.ts\nexport type User = {\n __typename?: 'User';\n id: string;\n name: string;\n email: string;\n posts: Array<Post>;\n};\n\nexport type Post = {\n __typename?: 'Post';\n id: string;\n title: string;\n content: string;\n};\n