Adding a New Microservice to Helix Platform
This guide walks through creating and deploying a new microservice in the Helix platform.
Overview
Adding a new service involves:
- Creating the service using the template
- Configuring the service (ports, environment variables)
- Adding to Docker Compose
- Updating API Gateway routing
- Adding to Terraform (ECR, ECS)
- Deploying to environments
Time Estimate: 2-4 hours for a basic service
Step 1: Clone Template Service
Use the template service as your starting point:
# Navigate to apps directory
cd apps
# Clone template
cp -r template-service your-new-service
# Clean up
cd your-new-service
rm -rf dist node_modules
Step 2: Configure Service
2.1 Update project.json
{
"name": "your-new-service",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/your-new-service/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"target": "node",
"compiler": "tsc",
"outputPath": "dist/apps/your-new-service",
"main": "apps/your-new-service/src/main.ts",
"tsConfig": "apps/your-new-service/tsconfig.app.json",
"assets": ["apps/your-new-service/src/assets"],
"webpackConfig": "apps/your-new-service/webpack.config.js"
}
},
"serve": {
"executor": "@nx/js:node",
"options": {
"buildTarget": "your-new-service:build"
}
}
},
"tags": ["type:service"]
}
2.2 Update README.md
# Your New Service
## Overview
Brief description of what this service does.
## Responsibilities
- Primary function 1
- Primary function 2
- Primary function 3
## Port
- **Port:** 3010 (choose next available port)
## Environment Variables
- `PORT` - Service port (default: 3010)
- `CENTRAL_DATABASE_URL` - Central database connection
- `REDIS_HOST` - Redis host
- `YOUR_API_KEY` - Any service-specific API keys
## API Endpoints
- `GET /health` - Health check
- `GET /api/v1/your-resource` - List resources
- `POST /api/v1/your-resource` - Create resource
## Dependencies
- `@helix/shared/*` - Shared libraries
- Any service-specific npm packages
2.3 Update main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app/app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Global prefix
app.setGlobalPrefix('api/v1');
// Validation
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
// CORS
app.enableCors({
origin: process.env.CORS_ORIGIN?.split(',') || '*',
credentials: true,
});
const port = process.env.PORT || 3010;
await app.listen(port);
console.log(`🚀 Your New Service running on http://localhost:${port}`);
}
bootstrap();
2.4 Choose a Port
Next available ports:
- 3000: API Gateway
- 3001: Auth Service
- 3002: Tenant Service
- 3003: User Service (not created yet)
- 3004: KIRA Service
- 3005: VERA Service
- 3006: Data Service (not created yet)
- 3007: Notification Service (not created yet)
- 3008: Admin Service
- 3009: File Service
- 3010: Your New Service ← Use this
Update in:
apps/your-new-service/src/main.ts(default port)apps/your-new-service/.env.example
Step 3: Add to Docker Compose
Update docker-compose.yml:
services:
# ... existing services
your-new-service:
build:
context: .
dockerfile: Dockerfile.template
args:
SERVICE_NAME: your-new-service
environment:
NODE_ENV: development
PORT: 3010
SERVICE_NAME: your-new-service
CENTRAL_DATABASE_URL: ${CENTRAL_DATABASE_URL}
WORKOS_API_KEY: ${WORKOS_API_KEY}
WORKOS_CLIENT_ID: ${WORKOS_CLIENT_ID}
REDIS_HOST: redis
REDIS_PORT: 6379
CORS_ORIGIN: ${CORS_ORIGIN:-*}
# Add service-specific env vars
YOUR_API_KEY: ${YOUR_API_KEY:-}
ports:
- '3010:3010'
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- helix-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3010/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Step 4: Update API Gateway Routing
Add routing in apps/api-gateway/src/proxy/setup-proxy.ts:
export function setupProxy(app: INestApplication) {
const httpAdapter = app.getHttpAdapter();
const server = httpAdapter.getInstance();
// ... existing routes
// Your new service
server.use(
'/api/v1/your-resource',
createProxyMiddleware({
target: process.env.YOUR_NEW_SERVICE_URL || 'http://localhost:3010',
changeOrigin: true,
logLevel: 'debug',
onProxyReq: (proxyReq, req) => {
// Add request ID
const requestId = (req as any).id || uuidv4();
proxyReq.setHeader('X-Request-ID', requestId);
},
onError: (err, req, res) => {
console.error('Proxy error for your-new-service:', err);
(res as any).status(502).json({
statusCode: 502,
message: 'Service temporarily unavailable',
service: 'your-new-service',
});
},
})
);
}
Update API Gateway's .env.example:
# Service URLs
YOUR_NEW_SERVICE_URL=http://your-new-service:3010
Update docker-compose.yml for API Gateway:
api-gateway:
# ... existing config
environment:
# ... existing vars
YOUR_NEW_SERVICE_URL: http://your-new-service:3010
Step 5: Add to Terraform
5.1 Add to ECR Module
Update terraform/modules/ecr/main.tf:
locals {
services = [
"api-gateway",
"auth-service",
"tenant-service",
"user-service",
"kira-service",
"vera-service",
"data-service",
"notification-service",
"your-new-service" # ADD HERE
]
}
5.2 Add to ECS Cluster Module
Update terraform/modules/ecs-cluster/main.tf:
locals {
services = {
# ... existing services
your-new-service = {
port = 3010
cpu = var.service_cpu
memory = var.service_memory
desired_count = var.service_desired_count
min_capacity = var.service_min_capacity
max_capacity = var.service_max_capacity
public_facing = false
health_check_path = "/health"
}
}
}
If your service needs specific secrets, update the secrets section in the task definition:
secrets = concat(
# Common secrets for all services
[...],
# Service-specific secrets
each.key == "your-new-service" ? [
{
name = "YOUR_API_KEY"
valueFrom = var.your_api_key_secret_arn
}
] : [],
)
Step 6: Update GitHub Actions
6.1 Add to Test Workflow
Update .github/workflows/test.yml:
docker-build:
strategy:
matrix:
service:
- api-gateway
- auth-service
- tenant-service
- user-service
- kira-service
- vera-service
- data-service
- notification-service
- your-new-service # ADD HERE
6.2 Add to Staging Deployment
Update .github/workflows/deploy-staging.yml:
build-and-push:
strategy:
matrix:
service:
# ... existing services
- your-new-service # ADD HERE
update-ecs-services:
strategy:
matrix:
service:
# ... existing services
- your-new-service # ADD HERE
6.3 Add to Production Deployment
Update .github/workflows/deploy-production.yml:
# Same changes as staging
build-and-push:
strategy:
matrix:
service:
- your-new-service # ADD HERE
update-ecs-services:
strategy:
matrix:
service:
- your-new-service # ADD HERE
Step 7: Add to Makefile
Update Makefile:
# Service list
SERVICES := api-gateway auth-service tenant-service user-service \
kira-service vera-service data-service notification-service \
your-new-service
# Service will automatically be included in:
# - make build
# - make test
# - make docker-build
# - make docker-up
Step 8: Test Locally
# Build the service
nx build your-new-service
# Run the service
nx serve your-new-service
# Or use Make
make run-service SERVICE=your-new-service
# Test in Docker
docker-compose up your-new-service
# Test entire stack
docker-compose up
Verify:
- Service starts without errors
- Health check responds:
curl http://localhost:3010/health - API Gateway routes to service:
curl http://localhost:3000/api/v1/your-resource
Step 9: Write Tests
Create tests in apps/your-new-service/src/:
// app.controller.spec.ts
describe('YourNewController', () => {
let controller: YourNewController;
let service: YourNewService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [YourNewController],
providers: [YourNewService],
}).compile();
controller = module.get<YourNewController>(YourNewController);
service = module.get<YourNewService>(YourNewService);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
describe('GET /api/v1/your-resource', () => {
it('should return a list of resources', async () => {
const result = await controller.findAll();
expect(Array.isArray(result)).toBe(true);
});
});
});
Run tests:
nx test your-new-service
Step 10: Deploy
Deploy to Staging
git checkout develop
git add .
git commit -m "feat: add your-new-service"
git push origin develop
GitHub Actions will automatically:
- Run tests
- Build Docker image
- Push to ECR
- Update Terraform (creates new ECS service)
- Deploy to staging
- Run health checks
Deploy to Production
git checkout main
git merge develop
git tag -a v1.1.0 -m "Add your-new-service"
git push origin v1.1.0
Workflow will require manual approval before deploying.
Service Checklist
Before deploying, verify:
- Service cloned from template
-
project.jsonupdated with correct name - Port chosen and configured
-
main.tsupdated - Health endpoint works
-
.env.examplecreated -
docker-compose.ymlupdated - API Gateway routing configured
- Terraform ECR module updated
- Terraform ECS module updated
- GitHub Actions workflows updated
- Makefile automatically includes service
- Tests written and passing
- README.md documentation complete
- Tested locally with
docker-compose up - Tested API Gateway routing
- Deployed to staging successfully
Service Template Structure
Your service should follow this structure:
apps/your-new-service/
├── src/
│ ├── main.ts # Application entry point
│ ├── app/
│ │ ├── app.module.ts # Root module
│ │ ├── app.controller.ts # Root controller
│ │ └── app.service.ts # Root service
│ ├── health/
│ │ ├── health.module.ts # Health check module
│ │ ├── health.controller.ts # Health endpoint
│ │ └── health.service.ts # Health logic
│ ├── config/ # Configuration
│ ├── entities/ # Prisma entities (if needed)
│ └── assets/
├── .env.example # Environment variables template
├── Dockerfile # Service Dockerfile (optional, uses template)
├── project.json # Nx project configuration
├── tsconfig.json # TypeScript config
├── jest.config.ts # Test configuration
└── README.md # Service documentation
Common Service Patterns
Pattern 1: Service with Database Access
// app.module.ts
import { Module } from '@nestjs/common';
import { DatabaseService } from '@helix/shared/database';
@Module({
imports: [],
providers: [
DatabaseService,
AppService,
],
controllers: [AppController],
})
export class AppModule {}
// app.service.ts
import { Injectable } from '@nestjs/common';
import { DatabaseService } from '@helix/shared/database';
@Injectable()
export class AppService {
constructor(private readonly db: DatabaseService) {}
async findAll() {
return this.db.tenant.yourModel.findMany();
}
}
Pattern 2: Service with External API
// app.module.ts
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
@Module({
imports: [HttpModule],
providers: [AppService, ExternalApiService],
controllers: [AppController],
})
export class AppModule {}
// external-api.service.ts
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
@Injectable()
export class ExternalApiService {
constructor(private readonly http: HttpService) {}
async callApi(): Promise<any> {
const { data } = await firstValueFrom(
this.http.get('https://api.example.com/endpoint', {
headers: {
'Authorization': `Bearer ${process.env.YOUR_API_KEY}`,
},
})
);
return data;
}
}
Pattern 3: Service with Redis Caching
// app.module.ts
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import { redisStore } from 'cache-manager-redis-store';
@Module({
imports: [
CacheModule.registerAsync({
useFactory: async () => ({
store: await redisStore({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || '6379'),
ttl: 300, // 5 minutes default
}),
}),
}),
],
providers: [AppService],
controllers: [AppController],
})
export class AppModule {}
Troubleshooting
Issue: Service not accessible via API Gateway
Check:
- Service is running:
docker-compose ps - Health endpoint works:
curl http://localhost:3010/health - API Gateway routing configured
- Network connectivity:
docker-compose exec api-gateway ping your-new-service
Issue: Service failing health checks
Debug:
# Check service logs
docker-compose logs -f your-new-service
# Access service directly
curl http://localhost:3010/health
# Check if service is listening
docker-compose exec your-new-service netstat -tlnp
Issue: Terraform plan shows service but won't deploy
Fix:
- Ensure service is in ECR module's
locals.serviceslist - Verify service is in ECS module's
locals.servicesmap - Check GitHub Actions built and pushed image to ECR
- Verify image tag matches what Terraform expects
Best Practices
DO ✅
- Use shared libraries - Import from
@helix/shared/* - Follow existing patterns - Look at similar services
- Keep services focused - Single responsibility principle
- Write comprehensive tests - Unit + integration
- Document endpoints - Update README with API docs
- Handle errors gracefully - Use exception filters
- Add health checks - Include dependencies (DB, Redis)
- Use TypeScript strict mode - Explicit types everywhere
- Follow naming conventions - kebab-case for service names
DON'T ❌
- Don't duplicate code - Use shared libraries
- Don't skip tests - Minimum 80% coverage
- Don't hardcode values - Use environment variables
- Don't expose internal errors - Use filters
- Don't skip documentation - Update README
- Don't use
anytypes - TypeScript strict mode - Don't forget health checks - Required for deployment
- Don't commit secrets - Use .env (gitignored)
Port Assignment Reference
| Port | Service | Status |
|---|---|---|
| 3000 | API Gateway | ✅ In use |
| 3001 | Auth Service | ✅ In use |
| 3002 | Tenant Service | ✅ In use |
| 3003 | User Service | 🔄 Reserved |
| 3004 | KIRA Service | ✅ In use |
| 3005 | VERA Service | ✅ In use |
| 3006 | Data Service | 🔄 Reserved |
| 3007 | Notification Service | 🔄 Reserved |
| 3008 | Admin Service | ✅ In use |
| 3009 | File Service | ✅ In use |
| 3010+ | Available | ⭕ Use next |
Example: Complete New Service
See apps/template-service/ for a complete example of a properly configured service with:
- Health checks
- Proper module structure
- Environment configuration
- Docker support
- Tests
Clone this as your starting point and customize for your needs.
Need Help?
- Template service: Check
apps/template-service/ - Similar service: Look at
apps/auth-service/orapps/kira-service/ - Shared code: See
docs/shared-libraries/ - Deployment: See
docs/deployment/deployment-guide.md