Introduction: Why TypeScript is Ideal for Microservices
Microservice architecture has become the standard for building complex, scalable, and fault-tolerant applications. TypeScript, thanks to its strict typing, rich ecosystem, and support for asynchrony, is one of the best languages for implementing microservices. It allows you to write code that is easier to refactor, test, and document, which is critically important when splitting a system into dozens of independent services.
In this article, we will analyze architectural principles, build a simple microservice on NestJS, configure inter-service communication via RabbitMQ, and package everything in Docker Compose. You will get a ready-made practical foundation to start with.
Microservice Architecture in TypeScript
Proper architecture is the key to success. For TypeScript microservices, three key layers are distinguished:
- Transport Layer — responsible for receiving and sending requests (HTTP, gRPC, message queues).
- Business Logic — pure functions and services independent of the framework.
- Data Layer — repositories and ORMs (TypeORM, Prisma).
Each microservice should be autonomous: have its own database, CI/CD, and deployment logic. Communication between services is usually implemented via asynchronous queues (RabbitMQ, Kafka) or synchronous calls (gRPC).
Practice: Creating a Microservice on NestJS
Step 1: Project Initialization
Install the NestJS CLI and create a new project:
npm i -g @nestjs/clinest new user-service --package-manager npmcd user-serviceStep 2: Adding Microservice Support
NestJS has built-in support for microservice transporters. Let's install RabbitMQ:
npm install @nestjs/microservices amqplib amqp-connection-managerLet's create main.ts that runs the service as a microservice:
import { NestFactory } from '@nestjs/core';import { MicroserviceOptions, Transport } from '@nestjs/microservices';import { AppModule } from './app.module';
async function bootstrap() { const app = await NestFactory.createMicroservice<MicroserviceOptions>( AppModule, { transport: Transport.RMQ, options: { urls: ['amqp://localhost:5672'], queue: 'user_queue', queueOptions: { durable: false }, }, }, ); await app.listen();}bootstrap();Step 3: Implementing the Controller
Let's create a simple controller that processes messages from the queue:
import { Controller } from '@nestjs/common';import { MessagePattern, Payload } from '@nestjs/microservices';
@Controller()export class UserController { @MessagePattern('user.create') createUser(@Payload() data: { name: string; email: string }): string { // Business logic for creating a user should go here return `User ${data.name} created`; }}Now any service can send a message to the user_queue with the pattern user.create and receive a response.
Inter-Service Communication: Synchronous and Asynchronous
Synchronous: gRPC
For high-load systems requiring strict typing and low latency, use gRPC. Install the package:
npm install @nestjs/microservices @grpc/grpc-js @grpc/proto-loaderDefine a .proto file:
syntax = "proto3";package user;service UserService { rpc GetUser (UserId) returns (User);}message UserId { int32 id = 1; }message User { string name = 1; string email = 2; }In the controller, use the @GrpcMethod decorator to handle the request.