Backoffice Platform Development Guide¶
This guide provides comprehensive information for developers working on the Backoffice Platform, including architecture, development practices, and deployment procedures.
Development Environment Setup¶
Prerequisites¶
- Node.js (18.0 or higher)
- npm or yarn package manager
- PostgreSQL (14.0 or higher)
- MongoDB (5.0 or higher)
- Redis (6.0 or higher)
- Elasticsearch (8.0 or higher)
- Docker and Docker Compose
- Git for version control
Local Development Setup¶
- Clone Repository
- Install Dependencies
# Install root dependencies
npm install
# Install frontend dependencies
cd packages/frontend
npm install
# Install backend dependencies
cd ../backend
npm install
# Install shared dependencies
cd ../shared
npm install
- Environment Configuration
# Copy environment templates
cp .env.example .env
cp packages/frontend/.env.example packages/frontend/.env
cp packages/backend/.env.example packages/backend/.env
- Start Services with Docker
# Start required services
docker-compose up -d postgres mongodb redis elasticsearch
# Wait for services to be ready
npm run wait-for-services
- Database Setup
# Run database migrations
npm run migrate
# Seed with development data
npm run seed:dev
# Create initial admin user
npm run create-admin
- Start Development Servers
# Start all services in development mode
npm run dev
# Or start individually:
# Backend API
npm run dev:backend
# Frontend
npm run dev:frontend
# Background workers
npm run dev:workers
Project Structure¶
backoffice-platform/
├── packages/
│ ├── frontend/ # React admin interface
│ │ ├── src/
│ │ │ ├── components/ # Reusable UI components
│ │ │ ├── pages/ # Page components
│ │ │ ├── layouts/ # Layout components
│ │ │ ├── hooks/ # Custom React hooks
│ │ │ ├── services/ # API client services
│ │ │ ├── store/ # Redux store
│ │ │ ├── utils/ # Frontend utilities
│ │ │ └── types/ # TypeScript types
│ │ ├── public/ # Static assets
│ │ └── tests/ # Frontend tests
│ ├── backend/ # Node.js API server
│ │ ├── src/
│ │ │ ├── controllers/ # Request handlers
│ │ │ ├── services/ # Business logic
│ │ │ ├── models/ # Database models
│ │ │ ├── middleware/ # Express middleware
│ │ │ ├── routes/ # API routes
│ │ │ ├── workers/ # Background jobs
│ │ │ ├── utils/ # Backend utilities
│ │ │ └── types/ # TypeScript types
│ │ ├── migrations/ # Database migrations
│ │ ├── seeds/ # Development data
│ │ └── tests/ # Backend tests
│ └── shared/ # Shared code and types
│ ├── types/ # Common TypeScript types
│ ├── constants/ # Shared constants
│ ├── utils/ # Shared utilities
│ └── validations/ # Shared validation schemas
├── infrastructure/ # Infrastructure as code
├── docs/ # Technical documentation
├── scripts/ # Build and deployment scripts
└── docker-compose.yml # Development services
Architecture Overview¶
Microservices Architecture¶
// Service registry and communication
interface ServiceRegistry {
userService: UserManagementService;
supportService: SupportTicketService;
analyticsService: AnalyticsService;
auditService: AuditLoggingService;
notificationService: NotificationService;
}
// Inter-service communication
export class ServiceBus {
async publish(event: DomainEvent): Promise<void> {
const subscribers = this.getSubscribers(event.type);
await Promise.all(
subscribers.map((subscriber) => subscriber.handle(event))
);
}
subscribe(eventType: string, handler: EventHandler): void {
this.subscribers.set(eventType, [
...(this.subscribers.get(eventType) || []),
handler,
]);
}
}
Database Architecture¶
-- Multi-database strategy
-- PostgreSQL for relational data
CREATE DATABASE backoffice_core;
CREATE DATABASE backoffice_audit;
-- Core business entities
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL,
permissions JSONB DEFAULT '[]',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE customers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
company_name VARCHAR(255) NOT NULL,
contact_email VARCHAR(255) NOT NULL,
subscription_tier VARCHAR(50) NOT NULL,
status VARCHAR(50) DEFAULT 'active',
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT NOW()
);
-- Support ticket system
CREATE TABLE support_tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID REFERENCES customers(id),
assigned_to UUID REFERENCES users(id),
title VARCHAR(500) NOT NULL,
description TEXT NOT NULL,
priority VARCHAR(20) DEFAULT 'medium',
status VARCHAR(50) DEFAULT 'open',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- MongoDB for flexible document storage
db.analytics_events.createIndex({ timestamp: 1, event_type: 1 })
db.system_logs.createIndex({ timestamp: 1, level: 1, service: 1 })
db.user_sessions.createIndex({ user_id: 1, expires_at: 1 })
Event-Driven Architecture¶
// Domain events
export interface DomainEvent {
id: string;
type: string;
aggregateId: string;
data: any;
timestamp: Date;
version: number;
}
// Event handlers
export class UserEventHandler {
async handleUserCreated(event: UserCreatedEvent): Promise<void> {
// Send welcome email
await this.emailService.sendWelcomeEmail(event.data.email);
// Create audit log
await this.auditService.log({
action: "user_created",
userId: event.data.id,
metadata: event.data,
});
// Update analytics
await this.analyticsService.track("user_created", event.data);
}
async handleUserRoleChanged(event: UserRoleChangedEvent): Promise<void> {
// Invalidate permissions cache
await this.cacheService.invalidate(`permissions:${event.aggregateId}`);
// Notify user of role change
await this.notificationService.notify(event.aggregateId, {
type: "role_changed",
message: `Your role has been updated to ${event.data.newRole}`,
});
}
}
Frontend Development¶
Component Architecture with Ant Design¶
// Admin dashboard component
import React from "react";
import { Card, Row, Col, Statistic, Table, Button } from "antd";
import {
UserOutlined,
TicketOutlined,
DashboardOutlined,
} from "@ant-design/icons";
import { useQuery } from "react-query";
interface AdminDashboardProps {
dateRange: [Date, Date];
refreshInterval?: number;
}
export const AdminDashboard: React.FC<AdminDashboardProps> = ({
dateRange,
refreshInterval = 30000,
}) => {
const { data: metrics, isLoading } = useQuery(
["dashboard-metrics", dateRange],
() => analyticsApi.getDashboardMetrics(dateRange),
{ refetchInterval: refreshInterval }
);
const { data: recentTickets } = useQuery("recent-tickets", () =>
supportApi.getRecentTickets(10)
);
return (
<div className="admin-dashboard">
<Row gutter={16} className="metrics-row">
<Col span={6}>
<Card>
<Statistic
title="Total Users"
value={metrics?.totalUsers}
prefix={<UserOutlined />}
loading={isLoading}
/>
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic
title="Open Tickets"
value={metrics?.openTickets}
prefix={<TicketOutlined />}
loading={isLoading}
/>
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic
title="System Health"
value={metrics?.systemHealth}
suffix="%"
prefix={<DashboardOutlined />}
loading={isLoading}
/>
</Card>
</Col>
<Col span={6}>
<Card>
<Statistic
title="Revenue"
value={metrics?.revenue}
prefix="$"
precision={2}
loading={isLoading}
/>
</Card>
</Col>
</Row>
<Card title="Recent Support Tickets" className="recent-tickets">
<Table
dataSource={recentTickets}
columns={[
{ title: "ID", dataIndex: "id", key: "id" },
{ title: "Customer", dataIndex: "customerName", key: "customer" },
{ title: "Subject", dataIndex: "title", key: "title" },
{ title: "Priority", dataIndex: "priority", key: "priority" },
{ title: "Status", dataIndex: "status", key: "status" },
{
title: "Actions",
key: "actions",
render: (_, record) => (
<Button type="link" href={`/support/tickets/${record.id}`}>
View
</Button>
),
},
]}
pagination={false}
/>
</Card>
</div>
);
};
Advanced State Management¶
// Redux store with RTK Query
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
// API slice for data fetching
export const backofficeApi = createApi({
reducerPath: "backofficeApi",
baseQuery: fetchBaseQuery({
baseUrl: "/api/v1/",
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).auth.token;
if (token) {
headers.set("authorization", `Bearer ${token}`);
}
return headers;
},
}),
tagTypes: ["User", "Customer", "Ticket", "Analytics"],
endpoints: (builder) => ({
getUsers: builder.query<User[], UsersQuery>({
query: (params) => ({
url: "users",
params,
}),
providesTags: ["User"],
}),
createUser: builder.mutation<User, CreateUserRequest>({
query: (user) => ({
url: "users",
method: "POST",
body: user,
}),
invalidatesTags: ["User"],
}),
getTickets: builder.query<Ticket[], TicketsQuery>({
query: (params) => ({
url: "support/tickets",
params,
}),
providesTags: ["Ticket"],
}),
}),
});
// UI state slice
interface UIState {
sidebarCollapsed: boolean;
currentTheme: "light" | "dark";
activeFilters: Record<string, any>;
notifications: Notification[];
}
export const uiSlice = createSlice({
name: "ui",
initialState: {
sidebarCollapsed: false,
currentTheme: "light",
activeFilters: {},
notifications: [],
} as UIState,
reducers: {
toggleSidebar: (state) => {
state.sidebarCollapsed = !state.sidebarCollapsed;
},
setTheme: (state, action: PayloadAction<"light" | "dark">) => {
state.currentTheme = action.payload;
},
setFilter: (state, action: PayloadAction<{ key: string; value: any }>) => {
state.activeFilters[action.payload.key] = action.payload.value;
},
addNotification: (state, action: PayloadAction<Notification>) => {
state.notifications.push(action.payload);
},
},
});
Custom Hooks for Business Logic¶
// Custom hook for user management
export const useUserManagement = () => {
const [createUser, { isLoading: isCreating }] =
backofficeApi.useCreateUserMutation();
const [updateUser, { isLoading: isUpdating }] =
backofficeApi.useUpdateUserMutation();
const [deleteUser, { isLoading: isDeleting }] =
backofficeApi.useDeleteUserMutation();
const handleCreateUser = useCallback(
async (userData: CreateUserRequest) => {
try {
const result = await createUser(userData).unwrap();
message.success("User created successfully");
return result;
} catch (error) {
message.error("Failed to create user");
throw error;
}
},
[createUser]
);
const handleBulkUserUpdate = useCallback(
async (updates: BulkUserUpdate[]) => {
const results = await Promise.allSettled(
updates.map((update) => updateUser(update).unwrap())
);
const successful = results.filter((r) => r.status === "fulfilled").length;
const failed = results.length - successful;
if (failed > 0) {
message.warning(`${successful} users updated, ${failed} failed`);
} else {
message.success(`All ${successful} users updated successfully`);
}
return results;
},
[updateUser]
);
return {
createUser: handleCreateUser,
bulkUpdate: handleBulkUserUpdate,
isCreating,
isUpdating,
isDeleting,
};
};
// Custom hook for support ticket management
export const useSupportTickets = (filters?: TicketFilters) => {
const {
data: tickets,
isLoading,
refetch,
} = backofficeApi.useGetTicketsQuery(filters);
const [updateTicket] = backofficeApi.useUpdateTicketMutation();
const [assignTicket] = backofficeApi.useAssignTicketMutation();
const handleTicketAssignment = useCallback(
async (ticketId: string, assigneeId: string) => {
try {
await assignTicket({ ticketId, assigneeId }).unwrap();
message.success("Ticket assigned successfully");
} catch (error) {
message.error("Failed to assign ticket");
}
},
[assignTicket]
);
const handleBulkStatusUpdate = useCallback(
async (ticketIds: string[], status: TicketStatus) => {
const updates = ticketIds.map((id) => ({ id, status }));
try {
await Promise.all(
updates.map((update) => updateTicket(update).unwrap())
);
message.success(`${ticketIds.length} tickets updated`);
refetch();
} catch (error) {
message.error("Failed to update tickets");
}
},
[updateTicket, refetch]
);
return {
tickets,
isLoading,
assignTicket: handleTicketAssignment,
bulkStatusUpdate: handleBulkStatusUpdate,
refetch,
};
};
Backend Development¶
Service Layer Architecture¶
// Base service class
export abstract class BaseService {
protected logger: Logger;
protected cache: CacheService;
protected eventBus: EventBus;
constructor(logger: Logger, cache: CacheService, eventBus: EventBus) {
this.logger = logger;
this.cache = cache;
this.eventBus = eventBus;
}
protected async withTransaction<T>(
operation: (trx: Transaction) => Promise<T>
): Promise<T> {
const trx = await this.db.transaction();
try {
const result = await operation(trx);
await trx.commit();
return result;
} catch (error) {
await trx.rollback();
throw error;
}
}
}
// User management service
export class UserService extends BaseService {
constructor(
private userRepository: UserRepository,
private roleService: RoleService,
logger: Logger,
cache: CacheService,
eventBus: EventBus
) {
super(logger, cache, eventBus);
}
async createUser(userData: CreateUserRequest): Promise<User> {
return this.withTransaction(async (trx) => {
// Validate user data
const validation = await this.validateUserData(userData);
if (!validation.isValid) {
throw new ValidationError(validation.errors);
}
// Check for existing user
const existingUser = await this.userRepository.findByEmail(
userData.email,
trx
);
if (existingUser) {
throw new ConflictError("User already exists");
}
// Create user
const user = await this.userRepository.create(
{
...userData,
id: generateId(),
createdAt: new Date(),
updatedAt: new Date(),
},
trx
);
// Assign default role if not specified
if (!userData.role) {
await this.roleService.assignRole(user.id, "user", trx);
}
// Publish event
await this.eventBus.publish(new UserCreatedEvent(user));
// Invalidate cache
await this.cache.invalidate("users:*");
this.logger.info("User created", { userId: user.id, email: user.email });
return user;
});
}
async getUsersWithPagination(
query: UsersQuery
): Promise<PaginatedResult<User>> {
const cacheKey = `users:query:${JSON.stringify(query)}`;
// Try cache first
const cached = await this.cache.get<PaginatedResult<User>>(cacheKey);
if (cached) {
return cached;
}
// Query database
const result = await this.userRepository.findWithPagination(query);
// Cache result
await this.cache.set(cacheKey, result, 300); // 5 minutes
return result;
}
async updateUserRole(
userId: string,
newRole: string,
updatedBy: string
): Promise<User> {
return this.withTransaction(async (trx) => {
const user = await this.userRepository.findById(userId, trx);
if (!user) {
throw new NotFoundError("User not found");
}
const oldRole = user.role;
const updatedUser = await this.userRepository.update(
userId,
{ role: newRole, updatedAt: new Date() },
trx
);
// Publish role change event
await this.eventBus.publish(
new UserRoleChangedEvent({
userId,
oldRole,
newRole,
updatedBy,
})
);
// Invalidate user cache
await this.cache.invalidate(`user:${userId}`);
await this.cache.invalidate("users:*");
this.logger.info("User role updated", {
userId,
oldRole,
newRole,
updatedBy,
});
return updatedUser;
});
}
}
Advanced Authentication & Authorization¶
// JWT service with refresh tokens
export class AuthService {
constructor(
private userService: UserService,
private tokenRepository: TokenRepository,
private config: AuthConfig
) {}
async authenticate(email: string, password: string): Promise<AuthResult> {
// Validate credentials
const user = await this.userService.validateCredentials(email, password);
if (!user) {
throw new AuthenticationError("Invalid credentials");
}
// Check if account is active
if (user.status !== "active") {
throw new AuthenticationError("Account is not active");
}
// Generate tokens
const accessToken = this.generateAccessToken(user);
const refreshToken = this.generateRefreshToken(user);
// Store refresh token
await this.tokenRepository.store(refreshToken, user.id, {
expiresAt: new Date(Date.now() + this.config.refreshTokenTTL),
userAgent: user.lastLoginUserAgent,
ipAddress: user.lastLoginIp,
});
// Update last login
await this.userService.updateLastLogin(user.id);
return {
user,
accessToken,
refreshToken,
expiresIn: this.config.accessTokenTTL,
};
}
async refreshToken(refreshToken: string): Promise<AuthResult> {
// Verify refresh token
const tokenData = await this.tokenRepository.findByToken(refreshToken);
if (!tokenData || tokenData.expiresAt < new Date()) {
throw new AuthenticationError("Invalid refresh token");
}
// Get user
const user = await this.userService.findById(tokenData.userId);
if (!user || user.status !== "active") {
throw new AuthenticationError("User not found or inactive");
}
// Generate new tokens
const newAccessToken = this.generateAccessToken(user);
const newRefreshToken = this.generateRefreshToken(user);
// Replace refresh token
await this.tokenRepository.replace(refreshToken, newRefreshToken, {
expiresAt: new Date(Date.now() + this.config.refreshTokenTTL),
});
return {
user,
accessToken: newAccessToken,
refreshToken: newRefreshToken,
expiresIn: this.config.accessTokenTTL,
};
}
private generateAccessToken(user: User): string {
return jwt.sign(
{
sub: user.id,
email: user.email,
role: user.role,
permissions: user.permissions,
iat: Math.floor(Date.now() / 1000),
},
this.config.jwtSecret,
{
expiresIn: this.config.accessTokenTTL / 1000,
issuer: "backoffice-platform",
audience: "backoffice-api",
}
);
}
}
// Permission-based authorization
export class AuthorizationService {
constructor(private roleService: RoleService) {}
async checkPermission(
userId: string,
permission: string,
resource?: string
): Promise<boolean> {
const userPermissions = await this.getUserPermissions(userId);
// Check direct permission
if (userPermissions.includes(permission)) {
return true;
}
// Check resource-specific permission
if (resource) {
const resourcePermission = `${permission}:${resource}`;
if (userPermissions.includes(resourcePermission)) {
return true;
}
}
// Check wildcard permissions
const wildcardPermission = permission.split(":")[0] + ":*";
if (userPermissions.includes(wildcardPermission)) {
return true;
}
return false;
}
async getUserPermissions(userId: string): Promise<string[]> {
const user = await this.userService.findById(userId);
if (!user) {
return [];
}
// Get role-based permissions
const rolePermissions = await this.roleService.getRolePermissions(
user.role
);
// Merge with user-specific permissions
const allPermissions = [...rolePermissions, ...user.permissions];
// Remove duplicates
return [...new Set(allPermissions)];
}
}
Testing Strategy¶
Comprehensive Testing Approach¶
// Unit tests with comprehensive mocking
describe("UserService", () => {
let userService: UserService;
let mockUserRepository: jest.Mocked<UserRepository>;
let mockRoleService: jest.Mocked<RoleService>;
let mockEventBus: jest.Mocked<EventBus>;
let mockCache: jest.Mocked<CacheService>;
beforeEach(() => {
mockUserRepository = createMockUserRepository();
mockRoleService = createMockRoleService();
mockEventBus = createMockEventBus();
mockCache = createMockCacheService();
userService = new UserService(
mockUserRepository,
mockRoleService,
createMockLogger(),
mockCache,
mockEventBus
);
});
describe("createUser", () => {
const validUserData = {
email: "test@example.com",
name: "Test User",
role: "user",
};
it("should create a user successfully", async () => {
mockUserRepository.findByEmail.mockResolvedValue(null);
mockUserRepository.create.mockResolvedValue({
id: "user-1",
...validUserData,
createdAt: new Date(),
updatedAt: new Date(),
});
const result = await userService.createUser(validUserData);
expect(result).toEqual(expect.objectContaining(validUserData));
expect(mockEventBus.publish).toHaveBeenCalledWith(
expect.any(UserCreatedEvent)
);
expect(mockCache.invalidate).toHaveBeenCalledWith("users:*");
});
it("should throw error if user already exists", async () => {
mockUserRepository.findByEmail.mockResolvedValue({
id: "existing-user",
email: validUserData.email,
} as User);
await expect(userService.createUser(validUserData)).rejects.toThrow(
ConflictError
);
});
});
});
// Integration tests
describe("User API Integration", () => {
let app: Application;
let testDb: TestDatabase;
let authToken: string;
beforeAll(async () => {
testDb = await createTestDatabase();
app = await createTestApp(testDb);
authToken = await createTestAuthToken("admin");
});
afterAll(async () => {
await testDb.cleanup();
});
beforeEach(async () => {
await testDb.reset();
});
describe("POST /api/users", () => {
it("should create a new user", async () => {
const userData = {
email: "newuser@example.com",
name: "New User",
role: "user",
};
const response = await request(app)
.post("/api/users")
.set("Authorization", `Bearer ${authToken}`)
.send(userData)
.expect(201);
expect(response.body.data).toMatchObject(userData);
// Verify user was created in database
const createdUser = await testDb.users.findByEmail(userData.email);
expect(createdUser).toBeTruthy();
});
it("should return 409 for duplicate email", async () => {
// Create user first
await testDb.users.create({
email: "duplicate@example.com",
name: "Existing User",
role: "user",
});
const response = await request(app)
.post("/api/users")
.set("Authorization", `Bearer ${authToken}`)
.send({
email: "duplicate@example.com",
name: "New User",
role: "user",
})
.expect(409);
expect(response.body.error).toContain("already exists");
});
});
});
// End-to-end tests
describe("User Management E2E", () => {
let page: Page;
beforeAll(async () => {
page = await browser.newPage();
await page.goto("http://localhost:3000");
await loginAsAdmin(page);
});
afterAll(async () => {
await page.close();
});
it("should create a new user through UI", async () => {
// Navigate to users page
await page.click('[data-testid="nav-users"]');
await page.waitForSelector('[data-testid="users-table"]');
// Click create user button
await page.click('[data-testid="create-user-btn"]');
await page.waitForSelector('[data-testid="create-user-form"]');
// Fill out form
await page.fill('[data-testid="user-email"]', "e2e-test@example.com");
await page.fill('[data-testid="user-name"]', "E2E Test User");
await page.selectOption('[data-testid="user-role"]', "user");
// Submit form
await page.click('[data-testid="submit-user"]');
// Verify success message
await page.waitForSelector('[data-testid="success-message"]');
// Verify user appears in table
await page.waitForSelector(
'[data-testid="users-table"] td:has-text("e2e-test@example.com")'
);
});
});
Performance Optimization¶
Database Performance¶
// Query optimization with proper indexing
export class UserRepository {
async findUsersWithComplexFilters(filters: UserFilters): Promise<User[]> {
let query = this.db
.select("users.*")
.from("users")
.leftJoin("user_roles", "users.id", "user_roles.user_id")
.leftJoin("roles", "user_roles.role_id", "roles.id");
// Apply filters with proper indexing
if (filters.search) {
query = query.whereRaw(
`to_tsvector('english', users.name || ' ' || users.email) @@ plainto_tsquery('english', ?)`,
[filters.search]
);
}
if (filters.role) {
query = query.where("roles.name", filters.role);
}
if (filters.status) {
query = query.where("users.status", filters.status);
}
if (filters.createdAfter) {
query = query.where("users.created_at", ">=", filters.createdAfter);
}
// Optimize with proper ordering and limits
return query
.orderBy("users.created_at", "desc")
.limit(filters.limit || 50)
.offset(filters.offset || 0);
}
}
// Caching strategy for frequently accessed data
export class CachedUserService extends UserService {
async getUserById(id: string): Promise<User | null> {
const cacheKey = `user:${id}`;
// Try cache first
const cached = await this.cache.get<User>(cacheKey);
if (cached) {
return cached;
}
// Fetch from database
const user = await this.userRepository.findById(id);
if (user) {
// Cache for 10 minutes
await this.cache.set(cacheKey, user, 600);
}
return user;
}
async invalidateUserCache(userId: string): Promise<void> {
await Promise.all([
this.cache.invalidate(`user:${userId}`),
this.cache.invalidate(`user:permissions:${userId}`),
this.cache.invalidate("users:*"), // Invalidate list caches
]);
}
}
Frontend Performance¶
// Component optimization with React.memo and useMemo
export const UserList = React.memo<UserListProps>(
({ users, onUserSelect, filters }) => {
// Memoize expensive calculations
const filteredUsers = useMemo(() => {
return users
.filter((user) => {
if (filters.search) {
const searchLower = filters.search.toLowerCase();
return (
user.name.toLowerCase().includes(searchLower) ||
user.email.toLowerCase().includes(searchLower)
);
}
return true;
})
.sort((a, b) => a.name.localeCompare(b.name));
}, [users, filters.search]);
// Virtualized list for large datasets
const rowRenderer = useCallback(
({ index, key, style }) => {
const user = filteredUsers[index];
return (
<div key={key} style={style}>
<UserCard user={user} onSelect={onUserSelect} />
</div>
);
},
[filteredUsers, onUserSelect]
);
return (
<AutoSizer>
{({ height, width }) => (
<List
height={height}
width={width}
rowCount={filteredUsers.length}
rowHeight={120}
rowRenderer={rowRenderer}
/>
)}
</AutoSizer>
);
}
);
// Optimized data fetching with React Query
export const useOptimizedUsers = (filters: UserFilters) => {
return useQuery(["users", filters], () => userApi.getUsers(filters), {
// Stale time: consider data fresh for 5 minutes
staleTime: 5 * 60 * 1000,
// Cache time: keep in cache for 30 minutes
cacheTime: 30 * 60 * 1000,
// Background refetch on window focus
refetchOnWindowFocus: false,
// Retry failed requests 3 times
retry: 3,
// Enable if query key changes
keepPreviousData: true,
// Transform data
select: useCallback((data: ApiResponse<User[]>) => {
return data.data.map((user) => ({
...user,
displayName: `${user.name} (${user.email})`,
}));
}, []),
});
};
Deployment & DevOps¶
Docker Multi-stage Build¶
# Backend Dockerfile with multi-stage build
FROM node:18-alpine AS dependencies
WORKDIR /app
COPY package*.json ./
COPY packages/backend/package*.json ./packages/backend/
COPY packages/shared/package*.json ./packages/shared/
RUN npm ci --only=production
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
COPY packages/ ./packages/
RUN npm ci
RUN npm run build
FROM node:18-alpine AS runtime
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY --from=builder /app/packages/backend/dist ./dist
COPY --from=builder /app/packages/shared/dist ./shared
USER nodejs
EXPOSE 3000
ENV NODE_ENV production
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
Kubernetes Deployment¶
# Complete Kubernetes deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: backoffice-backend
labels:
app: backoffice-backend
version: v1
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: backoffice-backend
template:
metadata:
labels:
app: backoffice-backend
version: v1
spec:
containers:
- name: backend
image: optim/backoffice-backend:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: database-secret
key: url
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-secret
key: url
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: auth-secret
key: jwt-secret
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 3
volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
volumes:
- name: config
configMap:
name: backoffice-config
imagePullSecrets:
- name: registry-secret
---
apiVersion: v1
kind: Service
metadata:
name: backoffice-backend-service
spec:
selector:
app: backoffice-backend
ports:
- port: 80
targetPort: 3000
protocol: TCP
type: ClusterIP
Monitoring & Observability¶
Comprehensive Logging¶
// Structured logging with correlation IDs
export class StructuredLogger {
constructor(private serviceName: string) {}
info(message: string, context: LogContext = {}): void {
this.log("info", message, context);
}
error(message: string, error: Error, context: LogContext = {}): void {
this.log("error", message, {
...context,
error: {
name: error.name,
message: error.message,
stack: error.stack,
},
});
}
private log(level: LogLevel, message: string, context: LogContext): void {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
service: this.serviceName,
correlationId: context.correlationId || generateCorrelationId(),
userId: context.userId,
requestId: context.requestId,
...context,
};
console.log(JSON.stringify(logEntry));
}
}
// Request correlation middleware
export const correlationMiddleware = (
req: Request,
res: Response,
next: NextFunction
): void => {
const correlationId =
(req.headers["x-correlation-id"] as string) || generateCorrelationId();
req.correlationId = correlationId;
res.setHeader("x-correlation-id", correlationId);
// Add to async local storage for deeper correlation
correlationStorage.run(correlationId, () => {
next();
});
};
Performance Monitoring¶
// Performance metrics collection
export class MetricsCollector {
private metrics = new Map<string, number>();
recordDuration(name: string, duration: number): void {
this.metrics.set(`${name}_duration`, duration);
}
increment(name: string, value: number = 1): void {
const current = this.metrics.get(name) || 0;
this.metrics.set(name, current + value);
}
gauge(name: string, value: number): void {
this.metrics.set(name, value);
}
getMetrics(): Record<string, number> {
return Object.fromEntries(this.metrics);
}
}
// Performance monitoring middleware
export const performanceMiddleware = (
req: Request,
res: Response,
next: NextFunction
): void => {
const start = Date.now();
res.on("finish", () => {
const duration = Date.now() - start;
metricsCollector.recordDuration("http_request_duration", duration);
metricsCollector.increment(`http_requests_total_${res.statusCode}`);
if (duration > 1000) {
logger.warn("Slow request detected", {
method: req.method,
url: req.url,
duration,
statusCode: res.statusCode,
});
}
});
next();
};
For development questions or support, contact the Backoffice Development Team at backoffice-dev@optim.com