TailStack’s backend architecture provides a production-ready foundation for building scalable, secure REST APIs with Express 5 and TypeScript.
Technology Stack
Express 5 Fast, minimalist web framework for Node.js
TypeScript 5.9 Type safety for reliable, maintainable code
Node Clustering Multi-core utilization for maximum performance
Security First Pre-configured CORS and security best practices
Package Versions
Extracted from packages/core/source/Server/package.json:
{
"dependencies" : {
"axios" : "^1.7.9" ,
"cookie-parser" : "^1.4.7" ,
"cors" : "^2.8.6" ,
"dotenv" : "^17.2.3" ,
"express" : "^5.2.1"
},
"devDependencies" : {
"@types/cookie-parser" : "^1.4.10" ,
"@types/express" : "^5.0.6" ,
"@types/node" : "^25.0.10" ,
"ts-node" : "^10.9.2" ,
"tsx" : "^4.21.0" ,
"typescript" : "^5.9.3"
}
}
Express 5 Application
The core Express app is configured with essential middleware:
import express from 'express' ;
import cookieParser from 'cookie-parser' ;
import { corsMiddleware } from './middlewares/cors' ;
import routes from './routes' ;
const app = express ();
// Middlewares
app . use ( corsMiddleware );
app . use ( express . json ());
app . use ( express . urlencoded ({ extended: true }));
app . use ( cookieParser ());
// Routes
app . use ( '/api' , routes );
// Health check endpoint
app . get ( '/health' , ( req , res ) => {
res . json ({ status: 'ok' , message: 'Server is running' });
});
// 404 handler
app . use (( req , res ) => {
res . status ( 404 ). json ({
error: 'Not Found' ,
message: `Route ${ req . path } not found` ,
});
});
export default app ;
corsMiddleware : Custom CORS configuration for cross-origin requestsexpress.json() : Parses incoming JSON request bodiesexpress.urlencoded() : Parses URL-encoded form datacookieParser() : Parses cookies from request headers
Node Clustering
TailStack implements Node.js clustering to utilize all CPU cores:
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 ) {
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 for maximum performance... \n ` );
for ( let i = 0 ; i < numCPUs ; i ++ ) {
cluster . fork ();
}
cluster . on ( 'exit' , ( worker , code , signal ) => {
console . log ( `⚠️ Worker ${ worker . process . pid } died (code: ${ code } , signal: ${ signal } ).` );
console . log ( `🔄 Restarting in ${ CLUSTER_CONFIG . RESTART_DELAY } ms...` );
setTimeout (() => {
cluster . fork ();
}, CLUSTER_CONFIG . RESTART_DELAY );
});
} else {
workerCallback ();
}
};
Clustering Benefits
Multi-Core Utilization Spawns one worker per CPU core for parallel request processing
Automatic Restart Workers automatically restart if they crash, ensuring high availability
Load Distribution Node’s built-in load balancer distributes requests across workers
Zero-Downtime Updates Rolling restarts enable deployments without downtime
Server Initialization
The server starts with clustering and graceful shutdown:
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
const gracefulShutdown = ( signal : string ) => {
console . log ( ` ${ signal } signal received: closing HTTP server for worker ${ process . pid } ` );
server . close (() => {
console . log ( `HTTP server closed for worker ${ process . pid } ` );
process . exit ( 0 );
});
};
process . on ( 'SIGTERM' , () => gracefulShutdown ( 'SIGTERM' ));
process . on ( 'SIGINT' , () => gracefulShutdown ( 'SIGINT' ));
});
Graceful shutdown ensures all in-flight requests complete before the server stops, preventing data loss and client errors.
CORS Configuration
Custom CORS middleware with environment-based origin control:
import { Request , Response , NextFunction } from 'express' ;
import { config } from '../config' ;
export const corsMiddleware = ( req : Request , res : Response , next : NextFunction ) => {
res . header ( 'Access-Control-Allow-Origin' , config . corsOrigin );
res . header ( 'Access-Control-Allow-Methods' , 'GET, POST, PUT, DELETE, OPTIONS' );
res . header ( 'Access-Control-Allow-Headers' , 'Origin, X-Requested-With, Content-Type, Accept, Authorization' );
if ( req . method === 'OPTIONS' ) {
return res . sendStatus ( 200 );
}
next ();
};
CORS Security
# Development - allow all origins
CORS_ORIGIN = *
# Production - restrict to your domain
CORS_ORIGIN = https://yourdomain.com
# Multiple origins (comma-separated)
CORS_ORIGIN = https://yourdomain.com,https://www.yourdomain.com
The middleware handles OPTIONS preflight requests automatically:
Browser sends OPTIONS request before actual request
Server responds with allowed methods and headers
Browser proceeds with actual request if allowed
TypeScript Configuration
{
"compilerOptions" : {
"target" : "ES2022" ,
"module" : "ES2022" ,
"moduleResolution" : "bundler" ,
"esModuleInterop" : true ,
"resolveJsonModule" : true ,
"outDir" : "dist" ,
"strict" : true ,
"skipLibCheck" : true ,
"forceConsistentCasingInFileNames" : true ,
"noEmit" : false ,
"allowImportingTsExtensions" : true ,
"baseUrl" : "." ,
"paths" : {
"@/*" : [ "./src/*" ]
}
},
"include" : [ "src/**/*.ts" ],
"exclude" : [ "node_modules" , "dist" ]
}
Path aliases (@/*) enable clean imports: import { UserService } from '@/services/user'
Security Best Practices
Environment Variables
# Server
PORT = 3000
NODE_ENV = production
# CORS
CORS_ORIGIN = https://yourdomain.com
# Database
DB_HOST = localhost
DB_PORT = 5432
DB_NAME = myapp
DB_USER = dbuser
DB_PASSWORD = securepassword
# JWT
JWT_SECRET = your-secret-key-here
JWT_REFRESH_SECRET = your-refresh-secret-here
Never commit .env files to version control. Use .env.example as a template.
Cookie Security
Cookie-parser enables secure cookie handling:
import cookieParser from 'cookie-parser'
app . use ( cookieParser ())
// Set secure cookie
res . cookie ( 'sessionId' , sessionId , {
httpOnly: true , // Prevents XSS attacks
secure: true , // HTTPS only
sameSite: 'strict' , // CSRF protection
maxAge: 3600000 // 1 hour
})
Request Validation
Always validate and sanitize user input:
import { z } from 'zod'
const userSchema = z . object ({
name: z . string (). min ( 1 ). max ( 100 ),
email: z . string (). email (),
age: z . number (). int (). min ( 18 ). max ( 120 )
})
app . post ( '/api/users' , async ( req , res ) => {
try {
const validatedData = userSchema . parse ( req . body )
// Proceed with validated data
} catch ( error ) {
res . status ( 400 ). json ({ error: 'Invalid input' })
}
})
Development Scripts
{
"scripts" : {
"dev" : "tsx watch src/server.ts" ,
"build" : "tsc" ,
"start" : "node dist/server.js" ,
"test" : "echo \" Error: no test specified \" && exit 1"
}
}
Running the Backend
# Development with hot reload
pnpm dev
# Build for production
pnpm build
# Start production server
pnpm start
API Structure
Recommended project structure for Express backend:
source/Server/
├── src/
│ ├── app.ts # Express app configuration
│ ├── server.ts # Server entry point
│ ├── config/ # Configuration files
│ │ └── index.ts
│ ├── cluster/ # Clustering logic
│ │ └── index.ts
│ ├── middlewares/ # Custom middleware
│ │ ├── cors.ts
│ │ ├── auth.ts
│ │ └── errorHandler.ts
│ ├── routes/ # Route definitions
│ │ ├── index.ts
│ │ ├── users.ts
│ │ └── auth.ts
│ ├── controllers/ # Request handlers
│ │ ├── userController.ts
│ │ └── authController.ts
│ ├── services/ # Business logic
│ │ ├── userService.ts
│ │ └── authService.ts
│ ├── models/ # Data models
│ │ └── User.ts
│ ├── utils/ # Helper functions
│ │ ├── logger.ts
│ │ └── errors.ts
│ └── types/ # TypeScript types
│ └── index.ts
├── package.json
├── tsconfig.json
└── .env
Best Practices
Use TypeScript Type safety prevents runtime errors and improves developer experience
Environment Variables Never hardcode secrets - always use environment variables
Error Handling Implement global error handlers and custom error classes
Health Checks Always expose /health endpoints for monitoring
Logging Use structured logging (Pino, Winston) for production
Rate Limiting Protect APIs from abuse with rate limiting middleware
Next Steps
Agent Skills Learn about pre-configured AI agent skills
Monorepo Management Understand PNPM workspaces and monorepo structure