Building Scalable Microservices with TypeScript and NestJS: A Step-by-Step Guide

Learn how to build scalable microservices using TypeScript and NestJS with practical, beginner-friendly steps.

Microservices architecture allows developers to build scalable and maintainable applications by breaking down a complex system into smaller, independent services. In this guide, you will learn how to create scalable microservices using TypeScript and NestJS, a progressive Node.js framework that makes building efficient and reliable server-side applications easier.

First, make sure you have Node.js and npm installed on your machine. Then, install the NestJS CLI globally using the following command:

typescript
npm install -g @nestjs/cli

Now, create a new NestJS project for your microservice:

typescript
nest new user-service

Navigate into your project directory:

typescript
cd user-service

NestJS supports various transport layers for microservices communication. For this beginner tutorial, we will use TCP transport, which is simple and effective for internal communication between microservices.

Let's set up the main microservice server. Open `src/main.ts` and update it as follows:

typescript
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.TCP,
    options: {
      host: '127.0.0.1',
      port: 3001,
    },
  });
  await app.listen();
  console.log('User microservice is listening on port 3001');
}
bootstrap();

Next, create a simple service that listens to messages and responds. Modify `src/app.service.ts` to handle a request pattern called 'get_user':

typescript
import { Injectable } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';

@Injectable()
export class AppService {
  // Simulate user data
  private users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ];

  @MessagePattern({ cmd: 'get_user' })
  getUser(data: { id: number }) {
    const user = this.users.find(user => user.id === data.id);
    return user || { error: 'User not found' };
  }
}

We also need to register the service in the module file. Confirm your `src/app.module.ts` contains the following:

typescript
import { Module } from '@nestjs/common';
import { AppService } from './app.service';

@Module({
  providers: [AppService],
})
export class AppModule {}

Now, we will create a separate client service that communicates with this user microservice. Create a new directory outside of your current project, for example `client-service`, and initialize another NestJS app:

typescript
nest new client-service
cd client-service

Install the microservices package if not included:

typescript
npm install --save @nestjs/microservices

In your client service, set up a client proxy to communicate with the user microservice. Modify `src/app.service.ts` like this:

typescript
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';

@Injectable()
export class AppService implements OnModuleInit {
  private client: ClientProxy;

  onModuleInit() {
    this.client = ClientProxyFactory.create({
      transport: Transport.TCP,
      options: { host: '127.0.0.1', port: 3001 },
    });
  }

  async getUser(id: number) {
    return this.client.send({ cmd: 'get_user' }, { id }).toPromise();
  }
}

In a controller, you can now invoke this service method to fetch user data:

typescript
import { Controller, Get, Param } from '@nestjs/common';
import { AppService } from './app.service';

@Controller('users')
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get(':id')
  async getUser(@Param('id') id: string) {
    const userId = parseInt(id, 10);
    return await this.appService.getUser(userId);
  }
}

Finally, start your user microservice by running `npm run start` inside `user-service` directory, and start the client service by running `npm run start` inside the `client-service` directory. Your client will now forward user-related HTTP requests to the user microservice using TCP.

This simple example shows how NestJS and TypeScript allow you to build microservices that communicate efficiently. You can expand this architecture by adding more microservices, using message brokers like RabbitMQ or Kafka, and applying advanced patterns such as service discovery and distributed tracing for better scalability.

With this foundation, you’re ready to explore building real-world scalable microservices using NestJS.