In modern web development, APIs are the backbone of communication between clients and servers. When building backend applications with NestJS, developers often face the decision of which API architecture to choose. The three most popular approaches are tRPC, REST, and GraphQL. Each comes with its own set of advantages, trade-offs, and use cases.
In this module, we will compare tRPC, REST, and GraphQL within the context of NestJS. We’ll explore their differences, how they fit into NestJS’s ecosystem, and help you decide which architecture is the best for your next project.
Table of Contents
- Introduction to tRPC, REST, and GraphQL
- REST in NestJS
- GraphQL in NestJS
- tRPC in NestJS
- Comparison: tRPC vs REST vs GraphQL
- When to Use Each Approach
- Conclusion
Introduction to tRPC, REST, and GraphQL
Before diving into the specifics of how these APIs can be implemented in NestJS, let’s briefly understand what each approach offers.
- REST (Representational State Transfer) is an architectural style that uses stateless, client-server communication over HTTP. REST APIs typically rely on HTTP methods (GET, POST, PUT, DELETE) to interact with resources, which are usually represented in JSON format.
- GraphQL is a query language for APIs that allows clients to request exactly the data they need. Unlike REST, where the server defines what data is returned, GraphQL empowers the client to specify the structure of the response. GraphQL also supports real-time communication via subscriptions.
- tRPC is a relatively new approach that allows you to build fully type-safe APIs using TypeScript. It provides a more direct connection between the client and server without the need for a separate schema or REST endpoint. With tRPC, the type safety that TypeScript provides extends to the API calls, ensuring consistency across the codebase.
REST in NestJS
Overview
REST is the most traditional and widely used API architecture. In NestJS, creating a RESTful API is straightforward thanks to its powerful Controllers and Services.
Key Characteristics of REST
- Standard HTTP methods: REST uses standard HTTP methods like GET, POST, PUT, DELETE to interact with resources.
- Resource-oriented: In REST, each URL represents a resource (e.g.,
/users
,/products
). - Stateless: Each request from the client contains all the information the server needs to understand and process it (e.g., authentication tokens).
- Caching: RESTful services can be easily cached using HTTP headers, making them suitable for public APIs.
RESTful API in NestJS Example
Here’s how to implement a basic RESTful API in NestJS:
users.controller.ts
tsCopyEditimport { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
@Post()
create(@Body() userData: CreateUserDto) {
return this.usersService.create(userData);
}
}
users.service.ts
tsCopyEditimport { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
private readonly users = [];
findAll() {
return this.users;
}
findOne(id: string) {
return this.users.find(user => user.id === id);
}
create(userData: CreateUserDto) {
this.users.push(userData);
return userData;
}
}
In this example:
- Controllers handle incoming requests and delegate business logic to Services.
- @Get(), @Post(), and other decorators define endpoints for interacting with resources.
GraphQL in NestJS
Overview
GraphQL is a powerful API query language that allows clients to specify exactly what data they need. In contrast to REST, where the server defines the structure of responses, GraphQL provides more flexibility for clients.
Key Characteristics of GraphQL
- Flexible Queries: Clients can request only the data they need, minimizing over-fetching and under-fetching.
- Strong Typing: The API schema is strongly typed, meaning both the client and server know the shape of the data exchanged.
- Single Endpoint: Unlike REST, which often requires multiple endpoints, GraphQL uses a single endpoint for all requests.
- Subscriptions: GraphQL supports real-time updates using subscriptions.
GraphQL in NestJS Example
NestJS has excellent support for GraphQL via the @nestjs/graphql
package. Here’s an example of how to create a simple GraphQL API in NestJS.
users.module.ts
tsCopyEditimport { Module } from '@nestjs/common';
import { UsersResolver } from './users.resolver';
import { UsersService } from './users.service';
@Module({
providers: [UsersResolver, UsersService],
})
export class UsersModule {}
users.resolver.ts
tsCopyEditimport { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { UsersService } from './users.service';
import { User } from './user.model';
import { CreateUserDto } from './dto/create-user.dto';
@Resolver(() => User)
export class UsersResolver {
constructor(private readonly usersService: UsersService) {}
@Query(() => [User])
async users() {
return this.usersService.findAll();
}
@Mutation(() => User)
async createUser(@Args('data') data: CreateUserDto) {
return this.usersService.create(data);
}
}
user.model.ts
tsCopyEditimport { ObjectType, Field, Int } from '@nestjs/graphql';
@ObjectType()
export class User {
@Field()
id: string;
@Field()
name: string;
@Field()
email: string;
}
users.service.ts
tsCopyEditimport { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
private readonly users = [];
findAll() {
return this.users;
}
create(userData: CreateUserDto) {
const newUser = { ...userData, id: Date.now().toString() };
this.users.push(newUser);
return newUser;
}
}
In this setup:
- Resolvers define the GraphQL schema and the logic for handling queries and mutations.
- @Query() and @Mutation() decorators map GraphQL queries and mutations to the service methods.
tRPC in NestJS
Overview
tRPC (TypeScript Remote Procedure Call) is a modern approach to building APIs that enables fully type-safe communication between the client and the server. tRPC doesn’t rely on REST or GraphQL; instead, it allows calling server functions directly from the client with no schema or API definitions required.
Key Characteristics of tRPC
- Type Safety: tRPC provides end-to-end type safety using TypeScript. The server and client share the same types, ensuring consistency across both.
- No Schema: Unlike REST and GraphQL, tRPC doesn’t require you to define schemas, which reduces boilerplate code.
- Direct RPC Calls: Clients can call server functions directly like regular function calls, but over HTTP.
tRPC in NestJS Example
tRPC is not natively supported in NestJS, but you can integrate it with NestJS using a third-party library like @trpc/server
.
1. Install the necessary packages
bashCopyEditnpm install @trpc/server @nestjs/core
2. Create a tRPC Router
tsCopyEditimport { createRouter } from '@trpc/server';
import { inferAsyncReturnType, initTRPC } from '@trpc/server';
const t = initTRPC.create();
const appRouter = t.router({
getUser: t.procedure.input((val: string) => val).query((opts) => {
return { id: opts.input, name: 'User' };
}),
createUser: t.procedure.input((input: { name: string }) => input).mutation((input) => {
return { id: '123', name: input.name };
}),
});
3. Setup the tRPC API in NestJS
tsCopyEditimport { NestFactory } from '@nestjs/core';
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { trpcMiddleware } from '@trpc/server/adapters/express';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use('/trpc', trpcMiddleware({ router: appRouter }));
await app.listen(3000);
}
bootstrap();
In this setup:
- t.procedure is used to define API methods, which can be invoked directly from the client with full type safety.
Comparison: tRPC vs REST vs GraphQL
Feature | tRPC | REST | GraphQL |
---|---|---|---|
Type Safety | End-to-end TypeScript type safety | None (can be added via TypeScript) | Strongly typed schemas (client and server) |
Schema Required | No schema required | Yes, with endpoints | Yes, GraphQL schema defines the API structure |
Communication Style | Remote procedure calls (RPC) | HTTP methods (GET, POST, PUT, DELETE) | Query language with flexible queries |
Overfetching | No over-fetching (as it’s RPC) | Potential for over-fetching | No over-fetching (client specifies data) |
Real-time Support | Not built-in | Not built-in | Built-in support with Subscriptions |
Learning Curve | Easy for TypeScript users | Simple and widely known | Steeper due to schema definition and queries |
Use Cases | Small to medium-sized apps, type safety-driven projects | Legacy systems, widely supported, simple APIs | Complex systems with varying data needs, real-time apps |
When to Use Each Approach
- Use tRPC when you need type-safe communication between the client and server, and you want to minimize boilerplate code. It’s perfect for TypeScript-based projects.
- Use REST when you need a simple, traditional approach to building APIs, and when the application doesn’t require a lot of real-time communication.
- Use GraphQL when you need flexible data querying, have complex data models, or want to support real-time updates via subscriptions.
Conclusion
In this module, we’ve compared tRPC, REST, and GraphQL in the context of NestJS. Each of these approaches has its own strengths and is suited for different scenarios. The choice between them will depend on your project requirements, the level of type safety you need, and whether you need real-time data or flexible querying capabilities.