Multi-Service Routing
A skill for configuring NestJS applications with multiple service paths and Swagger documentation routing.
Pattern Overview
Services are served under specific prefixes with their API docs at a consistent path:
- •Backend API:
https://domain.com/backend/v1/...→ Docs at/backend/api - •Station API:
https://domain.com/station/v1/...→ Docs at/station/api - •Admin API:
https://domain.com/admin/v1/...→ Docs at/admin/api
Bootstrap Configuration
main.ts Pattern
typescript
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter } from '@nestjs/platform-fastify';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ValidationPipe, VersioningType } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { useContainer } from 'class-validator';
import { AppModule } from './app.module';
export async function bootstrap() {
const fastifyAdapter = new FastifyAdapter();
// Register multipart for file uploads
await fastifyAdapter.register(require('@fastify/multipart'), {
attachFieldsToBody: false,
limits: {
fileSize: 20 * 1024 * 1024,
files: 1,
fieldSize: 1024,
},
});
const app = await NestFactory.create(AppModule, fastifyAdapter as any);
const configService = app.get(ConfigService);
useContainer(app.select(AppModule), { fallbackOnErrors: true });
// Get service prefix from environment (default: backend)
const servicePrefix = configService.get<string>('SERVICE_PREFIX', 'backend');
// Set global prefix
app.setGlobalPrefix(servicePrefix);
// Enable global validation pipe
app.useGlobalPipes(new ValidationPipe({ transform: true }));
// Configure CORS
const allowedOrigins = configService
.get<string>('CORS_ORIGIN', 'http://localhost:3000')
.split(',');
app.enableCors({
origin: allowedOrigins,
methods: 'GET, HEAD, PUT, PATCH, POST, DELETE, OPTIONS',
});
// Enable API versioning
app.enableVersioning({
type: VersioningType.URI,
});
// Configure Swagger
const swaggerConfig = new DocumentBuilder()
.setTitle(`${servicePrefix.toUpperCase()} API`)
.setDescription(`API documentation for ${servicePrefix} service`)
.addBearerAuth()
.addServer('http://localhost:3000', 'Local')
.addServer(`https://dev-origin-api.example.com`, 'Development')
.addServer(`https://api.example.com`, 'Production')
.build();
const document = SwaggerModule.createDocument(app as any, swaggerConfig);
// Swagger docs at /{service_prefix}/api
SwaggerModule.setup(`${servicePrefix}/api`, app as any, document, {
swaggerOptions: {
persistAuthorization: true,
tagsSorter: 'alpha',
operationsSorter: 'alpha',
},
});
const port = configService.get<number>('PORT', 3000);
await app.listen(port, '0.0.0.0');
console.log(`🚀 ${servicePrefix.toUpperCase()} service running on port ${port}`);
console.log(`📚 Swagger docs: http://localhost:${port}/${servicePrefix}/api`);
}
if (require.main === module) {
bootstrap();
}
Environment Configuration
.env Pattern
bash
# Service Configuration SERVICE_PREFIX=backend # backend, station, admin, etc. PORT=3000 # CORS Configuration CORS_ORIGIN=http://localhost:3000,https://example.com CORS_ORIGIN_REGEX=^https://.*\\.example\\.com$ # Database DATABASE_URL=postgresql://... # JWT JWT_SECRET=your-secret-key JWT_EXPIRES_IN=1h
Service-Specific Examples
Backend Service
.env:
bash
SERVICE_PREFIX=backend PORT=3000
Result:
- •API endpoints:
http://localhost:3000/backend/v1/users - •Swagger docs:
http://localhost:3000/backend/api
Station Service
.env:
bash
SERVICE_PREFIX=station PORT=3001
Result:
- •API endpoints:
http://localhost:3000/station/v1/sensors - •Swagger docs:
http://localhost:3000/station/api
Admin Service
.env:
bash
SERVICE_PREFIX=admin PORT=3002
Result:
- •API endpoints:
http://localhost:3000/admin/v1/dashboard - •Swagger docs:
http://localhost:3000/admin/api
Docker Compose Multi-Service Setup
docker-compose.yml Pattern
yaml
services:
backend:
build:
context: .
dockerfile: Dockerfile
container_name: backend-service
environment:
- SERVICE_PREFIX=backend
- PORT=3000
- DATABASE_URL=${DATABASE_URL}
ports:
- "3000:3000"
volumes:
- ./apps/backend:/app
- ./libs:/app/libs
station:
build:
context: .
dockerfile: Dockerfile
container_name: station-service
environment:
- SERVICE_PREFIX=station
- PORT=3001
- DATABASE_URL=${DATABASE_URL}
ports:
- "3001:3001"
volumes:
- ./apps/station:/app
- ./libs:/app/libs
admin:
build:
context: .
dockerfile: Dockerfile
container_name: admin-service
environment:
- SERVICE_PREFIX=admin
- PORT=3002
- DATABASE_URL=${DATABASE_URL}
ports:
- "3002:3002"
volumes:
- ./apps/admin:/app
- ./libs:/app/libs
# Reverse proxy (Nginx)
nginx:
image: nginx:alpine
container_name: api-gateway
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- backend
- station
- admin
Nginx Gateway Configuration
nginx.conf Pattern
nginx
events {
worker_connections 1024;
}
http {
upstream backend {
server backend:3000;
}
upstream station {
server station:3001;
}
upstream admin {
server admin:3002;
}
server {
listen 80;
server_name api.example.com;
# Backend service
location /backend {
rewrite ^/backend/(.*)$ /$1 break;
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Station service
location /station {
rewrite ^/station/(.*)$ /$1 break;
proxy_pass http://station;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Admin service
location /admin {
rewrite ^/admin/(.*)$ /$1 break;
proxy_pass http://admin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
Controller Pattern
Controllers automatically inherit the global prefix:
typescript
import { Controller, Get, Version } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
@ApiTags('Users')
@Controller('users')
export class UsersController {
@Get()
@Version('1')
@ApiOperation({ summary: 'Get all users' })
async findAll() {
// Accessible at: /{SERVICE_PREFIX}/v1/users
return [];
}
}
Results with SERVICE_PREFIX=backend:
- •Endpoint:
http://localhost:3000/backend/v1/users
Results with SERVICE_PREFIX=station:
- •Endpoint:
http://localhost:3000/station/v1/users
Config Service Pattern
Create an enum for environment variables:
typescript
// common/enum/environment.ts
export const ENVIRONMENT = {
SERVICE_PREFIX: 'SERVICE_PREFIX',
PORT: 'PORT',
CORS_ORIGIN: 'CORS_ORIGIN',
CORS_ORIGIN_REGEX: 'CORS_ORIGIN_REGEX',
DATABASE_URL: 'DATABASE_URL',
JWT_SECRET: 'JWT_SECRET',
JWT_EXPIRES_IN: 'JWT_EXPIRES_IN',
};
Testing Services
Test Each Service
bash
# Backend service curl http://localhost:3000/backend/v1/users curl http://localhost:3000/backend/api # Swagger UI # Station service curl http://localhost:3000/station/v1/sensors curl http://localhost:3000/station/api # Swagger UI # Admin service curl http://localhost:3000/admin/v1/dashboard curl http://localhost:3000/admin/api # Swagger UI
Benefits
- •Service Isolation: Each service has its own prefix
- •Consistent Docs: Swagger UI always at
/{service}/api - •Easy Routing: Nginx/LB can route by prefix
- •Versioning: URI versioning works seamlessly
- •Multi-Tenant: Each service can be deployed independently
Quick Start
- •
Set environment variable:
bashexport SERVICE_PREFIX=backend
- •
Run service:
bashnpm run start:dev
- •
Access API:
codeAPI: http://localhost:3000/backend/v1/... Docs: http://localhost:3000/backend/api