Skip to main content

Overview

The Node Template is TailStack’s backend-only architecture, providing a secure, scalable, and performance-optimized foundation for building REST APIs, GraphQL servers, and microservices.
This template is located at packages/node/ in the TailStack repository.

When to Use This Template

Choose the Node template when you:

API Development

Building:
  • REST APIs
  • GraphQL servers
  • WebSocket services
  • Real-time APIs

Microservices

Creating:
  • Independent microservices
  • Service-oriented architecture
  • Backend-only utilities
  • Background workers

Separate Frontend

When frontend is:
  • Different repository
  • Mobile app (iOS/Android)
  • Multiple frontends
  • Third-party consumers

Headless Services

For:
  • Headless CMS
  • Data processing services
  • Webhook handlers
  • Scheduled jobs/cron

Directory Structure

node/
├── .agent/                          # Gemini agent skills
│   └── skills/
│       └── nodejs-backend-patterns/
├── .claude/                         # Claude agent skills
│   └── skills/
│       └── nodejs-backend-patterns/
├── .cursor/                         # Cursor agent skills
│   └── skills/
│       └── nodejs-backend-patterns/
├── .opencode/                       # OpenCode agent skills
│   └── skills/
│       └── nodejs-backend-patterns/
├── .agents/                         # Trae agent skills
│   └── skills/
│       └── nodejs-backend-patterns/
├── src/
│   ├── cluster/                     # Node clustering logic
│   │   └── index.ts                 # Cluster initialization
│   ├── config/                      # Server configuration
│   │   └── index.ts                 # Environment config
│   ├── constant/                    # Application constants
│   │   └── cluster.ts               # Cluster settings
│   ├── controller/                  # Request controllers
│   │   └── weather.controller.ts    # Example controller
│   ├── middlewares/                 # Express middlewares
│   │   └── error.middleware.ts      # Error handling
│   ├── routes/                      # API route definitions
│   │   ├── index.ts                 # Route aggregator
│   │   └── weather.routes.ts        # Example routes
│   ├── services/                    # Business logic services
│   │   └── weather.service.ts       # Example service
│   ├── types/                       # TypeScript type definitions
│   │   └── index.ts                 # Shared types
│   ├── app.ts                       # Express application setup
│   └── server.ts                    # Server entry point
├── .env.example                     # Environment variable template
├── .gitignore
├── package.json
├── tsconfig.json                    # TypeScript configuration
└── README.md

Technology Stack

Core Dependencies

package.json
{
  "dependencies": {
    "express": "^5.2.1",
    "@types/express": "^5.0.6"
  }
}
Express 5 features:
  • Promise-based middleware support
  • Improved error handling
  • Better TypeScript types
  • Enhanced security defaults
  • Removed legacy APIs

Node Clustering Architecture

The template uses Node.js clustering to maximize performance across all CPU cores:

Why Clustering?

Multi-Core Utilization

Node.js is single-threaded by default. Clustering:
  • Spawns multiple worker processes
  • One worker per CPU core
  • Distributes load across workers
  • Maximizes hardware utilization

High Availability

Automatic recovery:
  • Workers restart on crash
  • Zero-downtime failures
  • Primary process monitors workers
  • Configurable restart delay

Performance

Real-world benefits:
  • 4-8x throughput on modern CPUs
  • Better request concurrency
  • Load balancing across cores
  • Reduced latency under load

Production Ready

Enterprise features:
  • Graceful shutdown handling
  • SIGTERM/SIGINT support
  • Worker health monitoring
  • Production-tested patterns

Cluster Implementation

src/cluster/index.ts
import cluster from 'node:cluster';
import { availableParallelism } from 'node:os';
import { config } from '../config';
import { CLUSTER_CONFIG } from '../constant/cluster';

export const initializeCluster = (workerCallback: () => void) => {
  if (cluster.isPrimary) {
    // Primary process: spawn workers
    const numCPUs = config.workers || availableParallelism();
    
    console.log(`🚀 Primary process ${process.pid} is running`);
    console.log(`📡 Environment: ${config.nodeEnv}`);
    console.log(`🌐 CORS enabled for: ${config.corsOrigin}`);
    console.log(`⚙️ Spawning ${numCPUs} workers...\n`);

    // Fork workers for each CPU
    for (let i = 0; i < numCPUs; i++) {
      cluster.fork();
    }

    // Auto-restart workers on failure
    cluster.on('exit', (worker, code, signal) => {
      console.log(`⚠️ Worker ${worker.process.pid} died`);
      console.log(`   Code: ${code}, Signal: ${signal}`);
      console.log(`🔄 Restarting in ${CLUSTER_CONFIG.RESTART_DELAY}ms...`);
      
      setTimeout(() => {
        console.log('🔄 Spawning new worker...');
        cluster.fork();
      }, CLUSTER_CONFIG.RESTART_DELAY);
    });
  } else {
    // Worker process: run the callback
    workerCallback();
  }
};
availableParallelism(): Returns the number of available CPU cores, accounting for CPU affinity and cgroup limits in containerized environments.

Cluster Configuration

src/constant/cluster.ts
export const CLUSTER_CONFIG = {
  RESTART_DELAY: 1000,        // 1 second delay before restart
  MAX_RESTARTS: 10,           // Maximum restarts per hour
  HEALTH_CHECK_INTERVAL: 30000 // 30 seconds
};

Server Architecture

Entry Point

src/server.ts
import app from './app';
import { config } from './config';
import { initializeCluster } from './cluster';

initializeCluster(() => {
  const server = app.listen(config.port, () => {
    console.log(`✅ Worker ${process.pid} started on port ${config.port}`);
  });

  // Graceful shutdown handling
  const gracefulShutdown = (signal: string) => {
    console.log(`${signal} signal received`);
    console.log(`Closing HTTP server for worker ${process.pid}`);
    
    server.close(() => {
      console.log(`HTTP server closed for worker ${process.pid}`);
      process.exit(0);
    });
    
    // Force shutdown after 10 seconds
    setTimeout(() => {
      console.error('Forcing shutdown...');
      process.exit(1);
    }, 10000);
  };

  process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
  process.on('SIGINT', () => gracefulShutdown('SIGINT'));
});

Express Application Setup

src/app.ts
import express from 'express';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import { config } from './config';
import routes from './routes';
import { errorHandler } from './middlewares/error.middleware';

const app = express();

// Security middleware
app.use(cors({
  origin: config.corsOrigin,
  credentials: true
}));

// Body parsing middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

// API routes
app.use('/api', routes);

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({ 
    status: 'ok', 
    worker: process.pid,
    uptime: process.uptime()
  });
});

// Error handling (must be last)
app.use(errorHandler);

export default app;

Layered Architecture

The template follows a clean, layered architecture:

API Routes

Define HTTP endpoints and map them to controllers.
src/routes/weather.routes.ts
import { Router } from 'express';
import { WeatherController } from '../controller/weather.controller';

const router = Router();
const weatherController = new WeatherController();

router.get('/', weatherController.getCurrentWeather);
router.get('/forecast', weatherController.getForecast);
router.get('/history/:city', weatherController.getHistory);

export default router;
src/routes/index.ts
import { Router } from 'express';
import weatherRoutes from './weather.routes';

const router = Router();

router.use('/weather', weatherRoutes);
// Add more routes here

export default router;

Configuration Management

Environment Variables

src/config/index.ts
import dotenv from 'dotenv';

dotenv.config();

export const config = {
  // Server
  nodeEnv: process.env.NODE_ENV || 'development',
  port: parseInt(process.env.PORT || '5000', 10),
  
  // Clustering
  workers: process.env.WORKERS 
    ? parseInt(process.env.WORKERS, 10) 
    : undefined,
  
  // CORS
  corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:5173',
  
  // External APIs
  weatherApiKey: process.env.WEATHER_API_KEY || '',
  
  // Database (example)
  databaseUrl: process.env.DATABASE_URL || '',
};

Environment Template

.env.example
# Server Configuration
NODE_ENV=development
PORT=5000

# Clustering
WORKERS=4

# CORS Configuration
CORS_ORIGIN=http://localhost:5173

# External API Keys
WEATHER_API_KEY=your_api_key_here

# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/db

Agent Skills

The Node template includes one specialized agent skill:

Node.js Backend Patterns

Location: .agent/skills/nodejs-backend-patterns/

Architecture Patterns

  • Layered architecture
  • Repository pattern
  • Dependency injection
  • Service-oriented design
  • SOLID principles

Error Handling

  • Centralized error middleware
  • Custom error classes
  • Operational vs programmer errors
  • Error logging strategies
  • Graceful degradation

Security

  • Input validation
  • SQL injection prevention
  • XSS protection
  • CSRF tokens
  • Rate limiting
  • Helmet.js integration

Performance

  • Clustering strategies
  • Caching with Redis
  • Database connection pooling
  • Query optimization
  • Load balancing

Development Workflow

Starting Development

pnpm install
pnpm dev
TSX watch mode:
  • Automatic restart on file changes
  • Fast compilation (no build step)
  • Source map support for debugging
  • All cluster workers restart together

Building for Production

pnpm build
TypeScript compiler outputs to dist/:
  • Preserves directory structure
  • Generates declaration files (.d.ts)
  • Source maps for production debugging
  • Type checking enforced

Running Production Build

pnpm start
Runs node dist/server.js with all cluster workers.

Testing Strategies

Test individual services and utilities:
__tests__/services/weather.service.test.ts
import { WeatherService } from '../../src/services/weather.service';

describe('WeatherService', () => {
  let service: WeatherService;
  
  beforeEach(() => {
    service = new WeatherService();
  });
  
  it('should fetch weather for a city', async () => {
    const weather = await service.getWeather('London');
    expect(weather).toHaveProperty('temperature');
    expect(weather.city).toBe('London');
  });
});

Deployment Options

Containerized Deployment

Dockerfile
FROM node:20-alpine

WORKDIR /app

COPY package*.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile

COPY . .
RUN pnpm build

EXPOSE 5000

CMD ["pnpm", "start"]
docker-compose.yml
version: '3.8'
services:
  api:
    build: .
    ports:
      - "5000:5000"
    environment:
      - NODE_ENV=production
      - WORKERS=4
    restart: unless-stopped

Comparison with Core Monorepo

FeatureNode TemplateCore Monorepo
Frontend❌ None✅ React included
Backend✅ Express + TS✅ Express + TS
Clustering✅ Full support✅ Full support
ComplexityLowerHigher
Use CaseAPIs/ServicesFull-stack apps
Skills1 skill3 skills
DeploymentBackend-onlyFull-stack or split

Next Steps

Core Monorepo

Compare with full-stack architecture

Project Structure

Explore detailed directory layout

Getting Started

Set up your first Node.js API

Deployment Guide

Deploy your backend service

Build docs developers (and LLMs) love