48 KiB
RE Workflow Management System - Backend Setup Guide
Version: 1.0
Date: October 16, 2025
Technology Stack: Node.js 22 LTS + TypeScript 5.7 + Express.js 4.21 + PostgreSQL 16
Table of Contents
- Backend Architecture Overview
- Technology Stack
- Project Folder Structure
- Database Schema Design
- API Endpoints Structure
- Authentication & Security
- Configuration Management
- Deployment Architecture
- Development Setup Instructions
1. Backend Architecture Overview
High-Level Architecture
┌─────────────────────────────────────────────────────────────┐
│ APPLICATION LAYER (Node.js) │
│ - Express.js REST API Server │
│ - Business Logic & Controllers │
│ - Service Layer (Workflow, Notification, Document) │
│ - AI Integration (Conclusion Generation) │
│ - Authentication Middleware (JWT + SSO) │
│ - Validation Layer (Zod) │
└─────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────┐
│ DATA LAYER (PostgreSQL) │
│ - Relational Database │
│ - Stored Procedures & Functions │
│ - Triggers for Activity Logging │
│ - Indexes for Performance │
└─────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────┐
│ STORAGE LAYER │
│ - Cloud Storage (GCP Cloud Storage) │
│ - Document Repository │
│ - Backup & Recovery System │
└─────────────────────────────────────────────────────────────┘
↕
┌─────────────────────────────────────────────────────────────┐
│ EXTERNAL SERVICES │
│ - SSO Authentication Provider │
│ - Email Service (SMTP) │
│ - AI Service (OpenAI) │
└─────────────────────────────────────────────────────────────┘
2. Technology Stack
| Component | Technology | Version | Purpose |
|---|---|---|---|
| Backend Runtime | Node.js | 22.x LTS | Server-side JavaScript runtime |
| Backend Language | TypeScript | 5.7.x | Type-safe backend development |
| Backend Framework | Express.js | 4.21.x | RESTful API framework |
| Database | PostgreSQL | 16.x | Primary relational database |
| ORM | Sequelize + TypeScript | 6.37.x | Database ORM with TypeScript support |
| Authentication | Passport.js | 0.7.x | SSO integration middleware |
| Validation | Zod | 3.24.x | TypeScript-first schema validation |
| File Upload | Multer | 1.4.x | Multipart file handling |
| Logging | Winston | 3.17.x | Application logging |
| API Documentation | Swagger/OpenAPI | 3.1 | API documentation |
| Testing | Jest + Supertest + ts-jest | 29.x | Unit & integration testing |
| Process Manager | PM2 | 5.4.x | Production process management |
| Code Quality | ESLint + Prettier + TypeScript ESLint | 9.x/3.x | Code linting & formatting |
3. Project Folder Structure
Backend Repository (re-workflow-backend)
Re_Backend/
│
├── src/ # Source code
│ ├── app.ts
│ ├── server.ts
│ ├── types/ # TypeScript type definitions
│ │ ├── index.ts
│ │ ├── express.d.ts
│ │ ├── user.types.ts
│ │ ├── workflow.types.ts
│ │ ├── approval.types.ts
│ │ ├── document.types.ts
│ │ ├── notification.types.ts
│ │ └── common.types.ts
│ │
│ ├── config/ # Configuration files
│ │ ├── database.ts
│ │ ├── jwt.ts
│ │ ├── sso.ts
│ │ ├── storage.ts
│ │ ├── email.ts
│ │ └── constants.ts
│ │
│ ├── controllers/ # Request handlers
│ │ ├── auth.controller.ts
│ │ ├── workflow.controller.ts
│ │ ├── approval.controller.ts
│ │ ├── document.controller.ts
│ │ ├── notification.controller.ts
│ │ ├── workNote.controller.ts
│ │ ├── participant.controller.ts
│ │ ├── dashboard.controller.ts
│ │ └── user.controller.ts
│ │
│ ├── services/ # Business logic layer
│ │ ├── auth.service.ts
│ │ ├── workflow.service.ts
│ │ ├── approval.service.ts
│ │ ├── tat.service.ts
│ │ ├── notification.service.ts
│ │ ├── document.service.ts
│ │ ├── workNote.service.ts
│ │ ├── participant.service.ts
│ │ ├── activity.service.ts
│ │ ├── ai.service.ts
│ │ ├── email.service.ts
│ │ └── storage.service.ts
│ │
│ ├── models/ # Sequelize models (ORM)
│ │ ├── index.ts
│ │ ├── User.ts
│ │ ├── WorkflowRequest.ts
│ │ ├── ApprovalLevel.ts
│ │ ├── Approver.ts
│ │ ├── Participant.ts
│ │ ├── Spectator.ts
│ │ ├── Document.ts
│ │ ├── WorkNote.ts
│ │ ├── Activity.ts
│ │ ├── Notification.ts
│ │ ├── TATTracking.ts
│ │ └── ConclusionRemark.ts
│ │
│ ├── routes/ # API route definitions
│ │ ├── index.ts
│ │ ├── auth.routes.ts
│ │ ├── workflow.routes.ts
│ │ ├── approval.routes.ts
│ │ ├── document.routes.ts
│ │ ├── notification.routes.ts
│ │ ├── workNote.routes.ts
│ │ ├── participant.routes.ts
│ │ ├── dashboard.routes.ts
│ │ └── user.routes.ts
│ │
│ ├── middlewares/ # Express middlewares
│ │ ├── auth.middleware.ts
│ │ ├── sso.middleware.ts
│ │ ├── validate.middleware.ts
│ │ ├── upload.middleware.ts
│ │ ├── rateLimiter.middleware.ts
│ │ ├── errorHandler.middleware.ts
│ │ ├── logger.middleware.ts
│ │ └── cors.middleware.ts
│ │
│ ├── validators/ # Request validation schemas (Zod)
│ │ ├── auth.validator.ts
│ │ ├── workflow.validator.ts
│ │ ├── approval.validator.ts
│ │ ├── document.validator.ts
│ │ ├── workNote.validator.ts
│ │ └── participant.validator.ts
│ │
│ ├── utils/ # Utility functions
│ │ ├── logger.ts
│ │ ├── responseHandler.ts
│ │ ├── errorHandler.ts
│ │ ├── dateCalculator.ts
│ │ ├── fileValidator.ts
│ │ ├── emailTemplate.ts
│ │ └── helpers.ts
│ │
│ ├── jobs/ # Background jobs & schedulers
│ │ ├── tatMonitor.job.ts
│ │ ├── reminderSender.job.ts
│ │ ├── dataCleanup.job.ts
│ │ └── reportGenerator.job.ts
│ │
│ └── seeders/ # Database seed data
│ ├── admin.seeder.ts
│ └── testData.seeder.ts
│
├── dist/ # Compiled JavaScript output
│
├── tests/ # Test suites
│ ├── unit/
│ │ ├── services/
│ │ ├── controllers/
│ │ └── utils/
│ ├── integration/
│ │ ├── api/
│ │ └── database/
│ └── setup.js
│
├── database/ # Database Scripts & Migrations
│ ├── migrations/
│ ├── seeders/
│ ├── schema/
│ │ └── schema.sql
│ └── README.md
│
├── logs/ # Application logs
│ ├── error.log
│ ├── combined.log
│ └── access.log
│
├── uploads/ # Temporary upload directory
│ └── .gitkeep
│
├── docs/ # API Documentation
│ ├── swagger/
│ └── postman/
│
├── scripts/ # Deployment & Utility Scripts
│ ├── deploy.sh
│ ├── backup.sh
│ ├── setup.sh
│ └── seed-db.sh
│
├── .env.example
├── .env.development
├── .env.production
├── .eslintrc.json
├── .prettierrc
├── .gitignore
├── .dockerignore
├── Dockerfile
├── docker-compose.yml
├── jest.config.js
├── tsconfig.json
├── nodemon.json
├── package.json
├── package-lock.json
└── README.md
4. Key Backend Files
4.1 TypeScript Configuration
tsconfig.json
{
"compilerOptions": {
"target": "ES2021",
"module": "commonjs",
"lib": ["ES2021"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"types": ["node", "jest"],
"typeRoots": ["./node_modules/@types", "./src/types"],
"baseUrl": "./src",
"paths": {
"@/*": ["./*"],
"@controllers/*": ["controllers/*"],
"@services/*": ["services/*"],
"@models/*": ["models/*"],
"@middlewares/*": ["middlewares/*"],
"@utils/*": ["utils/*"],
"@types/*": ["types/*"],
"@config/*": ["config/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests"]
}
4.2 Express Application
src/app.ts
import express, { Application, Request, Response } from 'express';
import helmet from 'helmet';
import cors from 'cors';
import morgan from 'morgan';
import routes from './routes';
import errorHandler from './middlewares/errorHandler.middleware';
import logger from './utils/logger';
const app: Application = express();
// Security middleware
app.use(helmet());
app.use(cors());
// Body parsing middleware
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Logging middleware
app.use(morgan('combined', { stream: logger.stream }));
// API routes
app.use('/api/v1', routes);
// Health check endpoint
app.get('/health', (req: Request, res: Response) => {
res.status(200).json({ status: 'OK', timestamp: new Date() });
});
// Error handling middleware (must be last)
app.use(errorHandler);
export default app;
4.3 Server Entry Point
src/server.ts
import app from './app';
import { sequelize } from './models';
import logger from './utils/logger';
const PORT: number = parseInt(process.env.PORT || '5000', 10);
// Database connection and server start
const startServer = async (): Promise<void> => {
try {
// Test database connection
await sequelize.authenticate();
logger.info('Database connection established successfully');
// Sync models (in development only)
if (process.env.NODE_ENV === 'development') {
await sequelize.sync({ alter: true });
logger.info('Database models synchronized');
}
// Start server
app.listen(PORT, () => {
logger.info(`Server running on port ${PORT}`);
logger.info(`Environment: ${process.env.NODE_ENV}`);
});
} catch (error) {
logger.error('Unable to start server:', error);
process.exit(1);
}
};
// Graceful shutdown
process.on('SIGTERM', async () => {
logger.info('SIGTERM signal received: closing HTTP server');
await sequelize.close();
process.exit(0);
});
startServer();
4.4 User Model Implementation
src/models/User.ts
import { DataTypes, Model, Optional } from 'sequelize';
import { sequelize } from './index';
interface UserAttributes {
userId: string;
employeeId: string;
email: string;
firstName: string;
lastName: string;
displayName: string;
department?: string;
designation?: string;
phone?: string;
reportingManagerId?: string;
isActive: boolean;
isAdmin: boolean;
lastLogin?: Date;
createdAt: Date;
updatedAt: Date;
}
interface UserCreationAttributes extends Optional<UserAttributes, 'userId' | 'department' | 'designation' | 'phone' | 'reportingManagerId' | 'lastLogin' | 'createdAt' | 'updatedAt'> {}
class User extends Model<UserAttributes, UserCreationAttributes> implements UserAttributes {
public userId!: string;
public employeeId!: string;
public email!: string;
public firstName!: string;
public lastName!: string;
public displayName!: string;
public department?: string;
public designation?: string;
public phone?: string;
public reportingManagerId?: string;
public isActive!: boolean;
public isAdmin!: boolean;
public lastLogin?: Date;
public createdAt!: Date;
public updatedAt!: Date;
// Associations
public reportingManager?: User;
public subordinates?: User[];
}
User.init(
{
userId: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
employeeId: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
comment: 'HR System Employee ID'
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING,
allowNull: false
},
displayName: {
type: DataTypes.STRING,
allowNull: false,
comment: 'Full Name for display'
},
department: {
type: DataTypes.STRING,
allowNull: true
},
designation: {
type: DataTypes.STRING,
allowNull: true
},
phone: {
type: DataTypes.STRING,
allowNull: true
},
reportingManagerId: {
type: DataTypes.UUID,
allowNull: true,
references: {
model: 'users',
key: 'userId'
},
comment: 'Self-reference to reporting manager'
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true,
comment: 'Account status'
},
isAdmin: {
type: DataTypes.BOOLEAN,
defaultValue: false,
comment: 'Super user flag'
},
lastLogin: {
type: DataTypes.DATE,
allowNull: true
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
},
{
sequelize,
modelName: 'User',
tableName: 'users',
timestamps: true,
indexes: [
{
unique: true,
fields: ['employeeId']
},
{
unique: true,
fields: ['email']
},
{
fields: ['reportingManagerId']
},
{
fields: ['department']
},
{
fields: ['isActive']
}
]
}
);
// Self-referencing association for reporting manager
User.belongsTo(User, {
as: 'reportingManager',
foreignKey: 'reportingManagerId',
targetKey: 'userId'
});
User.hasMany(User, {
as: 'subordinates',
foreignKey: 'reportingManagerId',
sourceKey: 'userId'
});
export { User };
Authentication Middleware
// src/middlewares/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { User } from '@models/User';
import { ssoConfig } from '@config/sso';
interface JwtPayload {
userId: string;
employeeId: string;
email: string;
role: string;
iat: number;
exp: number;
}
export const authenticateToken = async (
req: Request,
res: Response,
next: NextFunction
): Promise<void> => {
try {
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
res.status(401).json({
success: false,
message: 'Access token is required'
});
return;
}
// Verify JWT token
const decoded = jwt.verify(token, ssoConfig.jwtSecret) as JwtPayload;
// Fetch user from database to ensure they still exist and are active
const user = await User.findByPk(decoded.userId);
if (!user || !user.isActive) {
res.status(401).json({
success: false,
message: 'User not found or inactive'
});
return;
}
// Attach user info to request object
req.user = {
userId: user.userId,
email: user.email,
employeeId: user.employeeId,
role: user.isAdmin ? 'admin' : 'user'
};
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
res.status(401).json({
success: false,
message: 'Token has expired'
});
} else if (error.name === 'JsonWebTokenError') {
res.status(401).json({
success: false,
message: 'Invalid token'
});
} else {
res.status(500).json({
success: false,
message: 'Authentication error',
error: error.message
});
}
}
};
export const requireAdmin = (
req: Request,
res: Response,
next: NextFunction
): void => {
if (req.user?.role !== 'admin') {
res.status(403).json({
success: false,
message: 'Admin access required'
});
return;
}
next();
};
Authentication Routes
// src/routes/auth.routes.ts
import { Router } from 'express';
import { AuthController } from '@controllers/auth.controller';
import { authenticateToken } from '@middlewares/auth.middleware';
import { validateSSOCallback } from '@validators/auth.validator';
const router = Router();
const authController = new AuthController();
// SSO callback endpoint (no authentication required)
router.post('/sso-callback', authController.handleSSOCallback);
// Protected routes (require authentication)
router.get('/me', authenticateToken, authController.getCurrentUser);
router.post('/refresh', authController.refreshToken);
router.post('/logout', authenticateToken, authController.logout);
export default router;
4.5 Type Definitions
src/types/express.d.ts
import { JwtPayload } from 'jsonwebtoken';
declare global {
namespace Express {
interface Request {
user?: {
userId: string;
email: string;
employeeId: string;
role?: string;
};
file?: Express.Multer.File;
files?: Express.Multer.File[];
}
}
}
src/types/common.types.ts
export enum Priority {
STANDARD = 'STANDARD',
EXPRESS = 'EXPRESS'
}
export enum WorkflowStatus {
DRAFT = 'DRAFT',
PENDING = 'PENDING',
IN_PROGRESS = 'IN_PROGRESS',
APPROVED = 'APPROVED',
REJECTED = 'REJECTED',
CLOSED = 'CLOSED'
}
export enum ApprovalStatus {
PENDING = 'PENDING',
IN_PROGRESS = 'IN_PROGRESS',
APPROVED = 'APPROVED',
REJECTED = 'REJECTED',
SKIPPED = 'SKIPPED'
}
export enum ParticipantType {
SPECTATOR = 'SPECTATOR',
INITIATOR = 'INITIATOR',
APPROVER = 'APPROVER',
CONSULTATION = 'CONSULTATION'
}
export enum TATStatus {
ON_TRACK = 'ON_TRACK',
APPROACHING = 'APPROACHING',
BREACHED = 'BREACHED'
}
export interface ApiResponse<T = any> {
success: boolean;
message: string;
data?: T;
error?: string;
timestamp: Date;
}
export interface PaginationParams {
page: number;
limit: number;
sortBy?: string;
sortOrder?: 'ASC' | 'DESC';
}
export interface PaginatedResponse<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
};
}
5. Database Schema Design
[See Full Database Schema Section from Original Document]
The database schema includes:
users- User information from SSOworkflow_requests- Main workflow requestsapproval_levels- Approval hierarchyparticipants- Spectators and participantsdocuments- Document metadatawork_notes- Communication within workflowactivities- Activity lognotifications- User notificationstat_tracking- TAT monitoringconclusion_remarks- AI-generated conclusionsaudit_logs- System audit trailsystem_settings- Application configuration
6. API Endpoints Structure
Base URL: http://api.re-workflow.com/api/v1
6.1 Authentication APIs
POST /auth/sso-callback # Frontend sends SSO user data
POST /auth/logout # Logout and invalidate session
GET /auth/me # Get current user profile
POST /auth/refresh # Refresh JWT token
GET /auth/validate # Validate JWT token
6.2 Workflow Management APIs
GET /workflows
POST /workflows
GET /workflows/:id
PUT /workflows/:id
DELETE /workflows/:id
PATCH /workflows/:id/submit
PATCH /workflows/:id/close
GET /workflows/my-requests
GET /workflows/open
GET /workflows/closed
GET /workflows/assigned-to-me
6.3 Approval APIs
GET /workflows/:id/approvals
POST /workflows/:id/approvals
PATCH /workflows/:id/approvals/:levelId/approve
PATCH /workflows/:id/approvals/:levelId/reject
GET /workflows/:id/approvals/current
6.4 Participant APIs
GET /workflows/:id/participants
POST /workflows/:id/participants
DELETE /workflows/:id/participants/:participantId
GET /workflows/:id/spectators
6.5 Document APIs
GET /workflows/:id/documents
POST /workflows/:id/documents
GET /documents/:documentId
GET /documents/:documentId/download
DELETE /documents/:documentId
GET /documents/:documentId/preview
6.6 Work Notes APIs
GET /workflows/:id/work-notes
POST /workflows/:id/work-notes
PUT /work-notes/:noteId
DELETE /work-notes/:noteId
POST /work-notes/:noteId/reactions
POST /work-notes/:noteId/attachments
6.7 Activity Log APIs
GET /workflows/:id/activities
GET /workflows/:id/activities/:type
6.8 Notification APIs
GET /notifications
GET /notifications/unread
PATCH /notifications/:id/read
PATCH /notifications/mark-all-read
DELETE /notifications/:id
6.9 TAT Tracking APIs
GET /workflows/:id/tat
GET /tat/breached
GET /tat/approaching
6.10 Dashboard APIs
GET /dashboard/stats
GET /dashboard/recent
GET /dashboard/pending-actions
6.11 User Management APIs
GET /users
GET /users/search
GET /users/:id
GET /users/:id/requests
6.12 Conclusion APIs
POST /workflows/:id/conclusion/generate
PUT /workflows/:id/conclusion
GET /workflows/:id/conclusion
7. Authentication & Security
7.1 SSO Integration Flow (Frontend-Initiated)
The authentication flow is handled entirely on the frontend side. The backend receives user information from the frontend after successful SSO authentication and either creates a new user record or updates an existing one.
SSO Flow Architecture
┌─────────────────┐ SSO Auth ┌─────────────────┐ User Data ┌─────────────────┐
│ Frontend │ ────────────► │ SSO Provider │ ──────────────► │ Frontend │
│ Application │ │ (RE SSO) │ │ Application │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ │
│ User Info + JWT Token │
│ ──────────────────────────────────────────────────────────────────────► │
│ │
▼ ▼
┌─────────────────┐ POST /api/v1/auth/sso-callback ┌─────────────────┐
│ Backend API │ ◄─────────────────────────────────── │ Frontend │
│ Server │ │ Application │
└─────────────────┘ └─────────────────┘
│
│ Create/Update User Record
▼
┌─────────────────┐
│ PostgreSQL │
│ Database │
└─────────────────┘
SSO Configuration
// src/config/sso.ts
export interface SSOUserData {
employeeId: string;
email: string;
firstName: string;
lastName: string;
displayName: string;
department?: string;
designation?: string;
phone?: string;
reportingManagerId?: string;
}
export interface SSOConfig {
jwtSecret: string;
jwtExpiry: string;
refreshTokenExpiry: string;
sessionSecret: string;
allowedOrigins: string[];
}
const ssoConfig: SSOConfig = {
jwtSecret: process.env.JWT_SECRET || '',
jwtExpiry: '24h',
refreshTokenExpiry: '7d',
sessionSecret: process.env.SESSION_SECRET || '',
allowedOrigins: process.env.CORS_ORIGIN?.split(',') || ['http://localhost:3000']
};
export default ssoConfig;
7.2 Authentication Service Implementation
SSO Callback Handler
// src/services/auth.service.ts
import { User } from '@models/User';
import { SSOUserData } from '@config/sso';
import jwt from 'jsonwebtoken';
import { ssoConfig } from '@config/sso';
export class AuthService {
/**
* Handle SSO callback from frontend
* Creates new user or updates existing user based on employeeId
*/
async handleSSOCallback(userData: SSOUserData): Promise<{
user: User;
accessToken: string;
refreshToken: string;
}> {
try {
// Check if user exists by employeeId
let user = await User.findOne({
where: { employeeId: userData.employeeId }
});
if (user) {
// Update existing user
user = await user.update({
email: userData.email,
firstName: userData.firstName,
lastName: userData.lastName,
displayName: userData.displayName,
department: userData.department,
designation: userData.designation,
phone: userData.phone,
reportingManagerId: userData.reportingManagerId,
lastLogin: new Date(),
isActive: true
});
} else {
// Create new user
user = await User.create({
employeeId: userData.employeeId,
email: userData.email,
firstName: userData.firstName,
lastName: userData.lastName,
displayName: userData.displayName,
department: userData.department,
designation: userData.designation,
phone: userData.phone,
reportingManagerId: userData.reportingManagerId,
isActive: true,
isAdmin: false,
lastLogin: new Date()
});
}
// Generate JWT tokens
const accessToken = this.generateAccessToken(user);
const refreshToken = this.generateRefreshToken(user);
return {
user,
accessToken,
refreshToken
};
} catch (error) {
throw new Error(`SSO callback failed: ${error.message}`);
}
}
/**
* Generate JWT access token
*/
private generateAccessToken(user: User): string {
const payload = {
userId: user.userId,
employeeId: user.employeeId,
email: user.email,
role: user.isAdmin ? 'admin' : 'user'
};
return jwt.sign(payload, ssoConfig.jwtSecret, {
expiresIn: ssoConfig.jwtExpiry
});
}
/**
* Generate JWT refresh token
*/
private generateRefreshToken(user: User): string {
const payload = {
userId: user.userId,
type: 'refresh'
};
return jwt.sign(payload, ssoConfig.jwtSecret, {
expiresIn: ssoConfig.refreshTokenExpiry
});
}
/**
* Validate JWT token
*/
async validateToken(token: string): Promise<any> {
try {
return jwt.verify(token, ssoConfig.jwtSecret);
} catch (error) {
throw new Error('Invalid token');
}
}
/**
* Refresh access token using refresh token
*/
async refreshAccessToken(refreshToken: string): Promise<string> {
try {
const decoded = jwt.verify(refreshToken, ssoConfig.jwtSecret) as any;
if (decoded.type !== 'refresh') {
throw new Error('Invalid refresh token');
}
const user = await User.findByPk(decoded.userId);
if (!user || !user.isActive) {
throw new Error('User not found or inactive');
}
return this.generateAccessToken(user);
} catch (error) {
throw new Error('Token refresh failed');
}
}
}
SSO Callback Controller
// src/controllers/auth.controller.ts
import { Request, Response } from 'express';
import { AuthService } from '@services/auth.service';
import { SSOUserData } from '@config/sso';
import { validateSSOCallback } from '@validators/auth.validator';
export class AuthController {
private authService: AuthService;
constructor() {
this.authService = new AuthService();
}
/**
* Handle SSO callback from frontend
* POST /api/v1/auth/sso-callback
*/
async handleSSOCallback(req: Request, res: Response): Promise<void> {
try {
// Validate request body
const validatedData = validateSSOCallback(req.body);
const result = await this.authService.handleSSOCallback(validatedData);
res.status(200).json({
success: true,
message: 'Authentication successful',
data: {
user: {
userId: result.user.userId,
employeeId: result.user.employeeId,
email: result.user.email,
firstName: result.user.firstName,
lastName: result.user.lastName,
displayName: result.user.displayName,
department: result.user.department,
designation: result.user.designation,
isAdmin: result.user.isAdmin
},
accessToken: result.accessToken,
refreshToken: result.refreshToken
}
});
} catch (error) {
res.status(400).json({
success: false,
message: 'Authentication failed',
error: error.message
});
}
}
/**
* Get current user profile
* GET /api/v1/auth/me
*/
async getCurrentUser(req: Request, res: Response): Promise<void> {
try {
const user = req.user;
res.status(200).json({
success: true,
data: {
userId: user.userId,
employeeId: user.employeeId,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
displayName: user.displayName,
department: user.department,
designation: user.designation,
isAdmin: user.isAdmin,
lastLogin: user.lastLogin
}
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Failed to get user profile',
error: error.message
});
}
}
/**
* Refresh access token
* POST /api/v1/auth/refresh
*/
async refreshToken(req: Request, res: Response): Promise<void> {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
res.status(400).json({
success: false,
message: 'Refresh token is required'
});
return;
}
const newAccessToken = await this.authService.refreshAccessToken(refreshToken);
res.status(200).json({
success: true,
data: {
accessToken: newAccessToken
}
});
} catch (error) {
res.status(401).json({
success: false,
message: 'Token refresh failed',
error: error.message
});
}
}
/**
* Logout user
* POST /api/v1/auth/logout
*/
async logout(req: Request, res: Response): Promise<void> {
try {
// In a more sophisticated implementation, you might want to:
// 1. Add the token to a blacklist
// 2. Update user's last logout time
// 3. Clear any server-side sessions
res.status(200).json({
success: true,
message: 'Logout successful'
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Logout failed',
error: error.message
});
}
}
}
SSO Validation Schema
// src/validators/auth.validator.ts
import { z } from 'zod';
export const ssoCallbackSchema = z.object({
employeeId: z.string().min(1, 'Employee ID is required'),
email: z.string().email('Valid email is required'),
firstName: z.string().min(1, 'First name is required'),
lastName: z.string().min(1, 'Last name is required'),
displayName: z.string().min(1, 'Display name is required'),
department: z.string().optional(),
designation: z.string().optional(),
phone: z.string().optional(),
reportingManagerId: z.string().uuid().optional()
});
export const validateSSOCallback = (data: any) => {
return ssoCallbackSchema.parse(data);
};
7.3 Security Measures
| Security Layer | Implementation |
|---|---|
| Transport Security | HTTPS/TLS 1.3 |
| Authentication | Frontend SSO + JWT tokens |
| Authorization | Role-based access control (RBAC) |
| Token Storage | HTTP-only cookies + Local storage |
| CSRF Protection | CSRF tokens on state-changing operations |
| XSS Protection | Input sanitization + Content Security Policy |
| SQL Injection | Parameterized queries (Sequelize ORM) |
| Rate Limiting | Express rate limiter (100 req/15min) |
| File Upload Security | Type validation, size limits, virus scanning |
| Audit Logging | All actions logged with user context |
| Data Encryption | At-rest (database) and in-transit (TLS) |
| User Data Validation | Zod schema validation for SSO user data |
| Session Management | JWT with refresh token rotation |
8. Configuration Management
8.1 Environment Variables
# backend/.env.example
# Application
NODE_ENV=development
PORT=5000
API_VERSION=v1
BASE_URL=http://localhost:5000
# Database
DB_HOST=localhost
DB_PORT=5432
DB_NAME=re_workflow_db
DB_USER=postgres
DB_PASSWORD=postgres
DB_SSL=false
DB_POOL_MIN=2
DB_POOL_MAX=10
# SSO Configuration (Frontend-handled)
# Backend only needs JWT secrets for token validation
JWT_SECRET=your_jwt_secret_key_here_min_32_chars
JWT_EXPIRY=24h
REFRESH_TOKEN_SECRET=your_refresh_token_secret_here
REFRESH_TOKEN_EXPIRY=7d
# Session
SESSION_SECRET=your_session_secret_here_min_32_chars
# Cloud Storage (GCP)
GCP_PROJECT_ID=re-workflow-project
GCP_BUCKET_NAME=re-workflow-documents
GCP_KEY_FILE=./config/gcp-key.json
# Email Service (Optional)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=notifications@royalenfield.com
SMTP_PASSWORD=your_smtp_password
EMAIL_FROM=RE Workflow System <notifications@royalenfield.com>
# AI Service (for conclusion generation)
AI_API_KEY=your_ai_api_key
AI_MODEL=gpt-4
AI_MAX_TOKENS=500
# Logging
LOG_LEVEL=info
LOG_FILE_PATH=./logs
# CORS
CORS_ORIGIN=http://localhost:3000
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# File Upload
MAX_FILE_SIZE_MB=10
ALLOWED_FILE_TYPES=pdf,doc,docx,xls,xlsx,ppt,pptx,jpg,jpeg,png,gif
# TAT Monitoring
TAT_CHECK_INTERVAL_MINUTES=30
TAT_REMINDER_THRESHOLD_1=50
TAT_REMINDER_THRESHOLD_2=80
9. Deployment Architecture
9.1 Docker Compose for Local Development
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:16-alpine
container_name: re_workflow_db
environment:
POSTGRES_USER: ${DB_USER:-workflow_user}
POSTGRES_PASSWORD: ${DB_PASSWORD:-secure_password}
POSTGRES_DB: ${DB_NAME:-re_workflow_db}
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./database/schema:/docker-entrypoint-initdb.d
networks:
- re_workflow_network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-workflow_user}"]
interval: 10s
timeout: 5s
retries: 5
backend:
build:
context: .
dockerfile: Dockerfile
container_name: re_workflow_backend
environment:
NODE_ENV: development
DB_HOST: postgres
DB_PORT: 5432
DB_USER: ${DB_USER:-workflow_user}
DB_PASSWORD: ${DB_PASSWORD:-secure_password}
DB_NAME: ${DB_NAME:-re_workflow_db}
PORT: 5000
ports:
- "5000:5000"
depends_on:
postgres:
condition: service_healthy
volumes:
- ./logs:/app/logs
- ./uploads:/app/uploads
networks:
- re_workflow_network
restart: unless-stopped
volumes:
postgres_data:
networks:
re_workflow_network:
driver: bridge
9.2 Dockerfile
# Dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY tsconfig.json ./
# Install all dependencies (including devDependencies for build)
RUN npm ci
# Copy source code
COPY src ./src
# Build TypeScript to JavaScript
RUN npm run build
# =====================================
# Production Image
# =====================================
FROM node:22-alpine
WORKDIR /app
# Install PM2 globally
RUN npm install -g pm2
# Copy package files
COPY package*.json ./
# Install only production dependencies
RUN npm ci --only=production
# Copy compiled JavaScript from builder
COPY --from=builder /app/dist ./dist
# Create logs and uploads directories
RUN mkdir -p logs uploads
# Expose port
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:5000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# Start with PM2
CMD ["pm2-runtime", "start", "dist/server.js", "--name", "re-workflow-api"]
9.3 CI/CD Pipeline (GitHub Actions)
# .github/workflows/backend-deploy.yml
name: Backend CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
- name: Build TypeScript
run: npm run build
- name: Build Docker image
run: docker build -t gcr.io/re-project/re-workflow-backend:${{ github.sha }} .
- name: Push to GCR
run: docker push gcr.io/re-project/re-workflow-backend:${{ github.sha }}
10. Development Setup Instructions
10.1 Prerequisites
- Node.js: v22.x LTS
- PostgreSQL: v16.x
- npm: v10.x or higher
- TypeScript: v5.7.x (installed globally or as dev dependency)
- Git: Latest version
- Docker & Docker Compose (Optional)
- GCP Account (for cloud storage in production)
10.2 Local Development Setup
Step 1: Clone Repository
git clone https://github.com/royalenfield/re-workflow-backend.git
cd re-workflow-backend
Step 2: Setup Database
# Install PostgreSQL (if not installed)
# For Ubuntu/Debian:
# sudo apt-get install postgresql-16
# For macOS:
# brew install postgresql@16
# Create database
createdb re_workflow_db
# Or using psql:
psql -U postgres
CREATE DATABASE re_workflow_db;
\q
# Run schema
psql -U postgres -d re_workflow_db -f database/schema/schema.sql
Step 3: Configure Environment
# Copy environment file
cp .env.example .env
# Edit .env with your configurations
nano .env # or use your preferred editor
# Required variables:
# - Database credentials
# - JWT secrets
# - SSO configuration
# - GCP storage keys
Step 4: Install Dependencies & Run
# Install dependencies
npm install
# Run TypeScript type checking
npm run type-check
# Run migrations (if using Sequelize CLI)
npx sequelize-cli db:migrate
# Seed data (optional)
npx sequelize-cli db:seed:all
# Start development server (with hot reload)
npm run dev
# Backend will run on: http://localhost:5000
# API Documentation: http://localhost:5000/api-docs
10.3 Docker Setup
# From backend repository root
cd re-workflow-backend
# Copy environment file
cp .env.example .env
# Edit .env if needed
nano .env
# Build and start services (backend + PostgreSQL)
docker-compose up --build -d
# Check logs
docker-compose logs -f
# Access backend: http://localhost:5000
# Stop services
docker-compose down
# Stop and remove volumes (clean state)
docker-compose down -v
10.4 Running Tests
# From backend repository
cd re-workflow-backend
npm test # Run all tests (Jest + ts-jest)
npm run test:unit # Unit tests only
npm run test:integration # Integration tests only
npm run test:watch # Watch mode for development
npm run test:coverage # With coverage report
# Coverage report will be in: coverage/
# View HTML report: open coverage/lcov-report/index.html
10.5 Code Quality Checks
# From backend repository
cd re-workflow-backend
npm run lint # ESLint check (TypeScript rules)
npm run lint:fix # Auto-fix issues
npm run format # Prettier formatting
npm run type-check # TypeScript type checking only (no compilation)
# Run all quality checks together
npm run lint && npm run type-check && npm test
11. Package.json
{
"name": "re-workflow-backend",
"version": "1.0.0",
"description": "Royal Enfield Workflow Management System - Backend API (TypeScript)",
"main": "dist/server.js",
"scripts": {
"start": "node dist/server.js",
"dev": "nodemon --exec ts-node src/server.ts",
"build": "tsc",
"build:watch": "tsc --watch",
"start:prod": "NODE_ENV=production node dist/server.js",
"test": "jest --coverage",
"test:unit": "jest --testPathPattern=tests/unit",
"test:integration": "jest --testPathPattern=tests/integration",
"test:watch": "jest --watch",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint src/**/*.ts --fix",
"format": "prettier --write \"src/**/*.ts\"",
"type-check": "tsc --noEmit",
"db:migrate": "sequelize-cli db:migrate",
"db:migrate:undo": "sequelize-cli db:migrate:undo",
"db:seed": "sequelize-cli db:seed:all",
"clean": "rm -rf dist"
},
"dependencies": {
"express": "^4.21.2",
"pg": "^8.13.1",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.5",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"zod": "^3.24.1",
"multer": "^1.4.5-lts.1",
"winston": "^3.17.0",
"cors": "^2.8.5",
"helmet": "^8.0.0",
"dotenv": "^16.4.7",
"express-rate-limit": "^7.5.0",
"morgan": "^1.10.0",
"@google-cloud/storage": "^7.14.0",
"axios": "^1.7.9",
"node-cron": "^3.0.3"
},
"devDependencies": {
"@types/express": "^5.0.0",
"@types/node": "^22.10.5",
"@types/bcryptjs": "^2.4.6",
"@types/jsonwebtoken": "^9.0.7",
"@types/passport": "^1.0.16",
"@types/passport-jwt": "^4.0.1",
"@types/multer": "^1.4.12",
"@types/cors": "^2.8.17",
"@types/morgan": "^1.9.9",
"@types/jest": "^29.5.14",
"@types/supertest": "^6.0.2",
"typescript": "^5.7.2",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"nodemon": "^3.1.9",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"supertest": "^7.0.0",
"eslint": "^9.17.0",
"@typescript-eslint/eslint-plugin": "^8.19.1",
"@typescript-eslint/parser": "^8.19.1",
"prettier": "^3.4.2",
"sequelize-cli": "^6.6.2",
"sequelize-typescript": "^2.1.7"
},
"engines": {
"node": ">=22.0.0",
"npm": ">=10.0.0"
}
}
Summary
This backend documentation provides:
✅ Complete Backend Architecture - Server structure, layers, and components
✅ Technology Stack - Node.js 22 LTS + TypeScript 5.7 + PostgreSQL 16
✅ Folder Structure - Detailed organization with TypeScript conventions
✅ Type Definitions - Comprehensive type safety with interfaces and enums
✅ Database Schema - Full PostgreSQL schema with tables, indexes, triggers
✅ API Specification - RESTful endpoints for all features
✅ Frontend SSO Integration - Frontend-handled authentication with backend user management
✅ User Management - Create/update users based on SSO data from frontend
✅ JWT Authentication - Token-based authentication with refresh token support
✅ Security Implementation - JWT validation, RBAC, audit trails
✅ Configuration Management - Environment variables and settings
✅ Deployment Architecture - Docker, docker-compose, CI/CD pipelines
✅ Development Setup - Step-by-step installation and configuration
✅ Testing Strategy - Unit and integration testing with ts-jest
✅ Code Quality - ESLint, Prettier, TypeScript best practices
Technology Stack: Node.js 22 LTS + TypeScript 5.7 + Express.js 4.21 + PostgreSQL 16
Authentication Flow: Frontend SSO → Backend User Creation/Update → JWT Token Generation
Repository: re-workflow-backend (Independent Repository)
Status: ✅ Ready for Implementation