Skip to main content

Coding Standards

This document outlines the coding standards and best practices for HMP development.

TypeScript Guidelines​

General Principles​

  • Use TypeScript strict mode
  • Prefer const over let, avoid var
  • Use meaningful variable and function names
  • Keep functions small and focused
  • Write self-documenting code

Type Definitions​

// DO: Use explicit types
interface Guest {
id: string;
firstName: string;
lastName: string;
email: string;
phone?: string;
}

// DO: Use enums for fixed values
enum ReservationStatus {
PENDING = 'PENDING',
CONFIRMED = 'CONFIRMED',
CHECKED_IN = 'CHECKED_IN',
CHECKED_OUT = 'CHECKED_OUT',
CANCELLED = 'CANCELLED',
}

// DON'T: Use any
function processData(data: any) { } // Bad
function processData(data: unknown) { } // Better
function processData(data: GuestData) { } // Best

Function Signatures​

// DO: Use clear return types
async function getReservation(id: string): Promise<Reservation | null> {
return await reservationRepository.findById(id);
}

// DO: Use destructuring for options
interface CreateReservationOptions {
guestId: string;
arrivalDate: Date;
departureDate: Date;
rooms: RoomRequest[];
}

async function createReservation({
guestId,
arrivalDate,
departureDate,
rooms,
}: CreateReservationOptions): Promise<Reservation> {
// Implementation
}

Error Handling​

// DO: Use custom error classes
class ReservationNotFoundError extends Error {
constructor(id: string) {
super(`Reservation not found: ${id}`);
this.name = 'ReservationNotFoundError';
}
}

// DO: Handle errors explicitly
async function getReservation(id: string): Promise<Reservation> {
const reservation = await repository.findById(id);
if (!reservation) {
throw new ReservationNotFoundError(id);
}
return reservation;
}

// DON'T: Swallow errors
try {
await someOperation();
} catch (error) {
// Bad: Error is lost
}

// DO: Log and rethrow or handle appropriately
try {
await someOperation();
} catch (error) {
logger.error('Operation failed', { error });
throw error;
}

Naming Conventions​

Files and Directories​

# Files: kebab-case
reservation-service.ts
guest-repository.ts
create-reservation.handler.ts

# Test files: same name with .test or .spec
reservation-service.test.ts
guest-repository.spec.ts

# Directories: kebab-case
/services/reservation-service/
/handlers/guest-handlers/

Code Elements​

// Classes: PascalCase
class ReservationService { }

// Interfaces: PascalCase (no I prefix)
interface Reservation { }
interface CreateReservationOptions { }

// Functions: camelCase
function createReservation() { }
function getGuestById() { }

// Variables: camelCase
const reservationId = '123';
let totalAmount = 0;

// Constants: UPPER_SNAKE_CASE
const MAX_ROOM_OCCUPANCY = 4;
const DEFAULT_CHECKOUT_TIME = '11:00';

// Enums: PascalCase with UPPER_SNAKE values
enum PaymentStatus {
PENDING = 'PENDING',
COMPLETED = 'COMPLETED',
}

Code Organization​

Service Layer​

// services/reservation-service.ts
export class ReservationService {
constructor(
private readonly repository: ReservationRepository,
private readonly guestService: GuestService,
private readonly eventBus: EventBus,
) {}

async create(options: CreateReservationOptions): Promise<Reservation> {
// Validate
await this.validateAvailability(options);

// Create
const reservation = await this.repository.create(options);

// Publish event
await this.eventBus.publish('reservation.created', { reservation });

return reservation;
}

private async validateAvailability(options: CreateReservationOptions): Promise<void> {
// Validation logic
}
}

Repository Layer​

// repositories/reservation-repository.ts
export class ReservationRepository {
constructor(private readonly db: Database) {}

async findById(id: string): Promise<Reservation | null> {
return await this.db.reservation.findUnique({
where: { id },
include: { guest: true, rooms: true },
});
}

async create(data: CreateReservationData): Promise<Reservation> {
return await this.db.reservation.create({
data,
include: { guest: true, rooms: true },
});
}
}

Handler Layer​

// handlers/create-reservation.handler.ts
import { Request, Response, NextFunction } from 'express';

export function createReservationHandler(
reservationService: ReservationService,
) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const data = createReservationSchema.parse(req.body);
const reservation = await reservationService.create(data);
res.status(201).json({ data: reservation });
} catch (error) {
next(error);
}
};
}

API Response Format​

Success Responses​

// Single item
{
"data": {
"id": "res_123",
"status": "CONFIRMED",
...
}
}

// Collection
{
"data": [...],
"pagination": {
"cursor": "abc123",
"hasMore": true
}
}

// Empty success
{
"success": true
}

Error Responses​

// Validation error
{
"error": "validation_error",
"message": "Invalid request data",
"details": [
{
"field": "arrivalDate",
"message": "Arrival date must be in the future"
}
]
}

// Not found
{
"error": "not_found",
"message": "Reservation not found"
}

// Server error
{
"error": "internal_error",
"message": "An unexpected error occurred",
"requestId": "req_abc123"
}

Database Queries​

Query Best Practices​

// DO: Use specific selects
const guest = await db.guest.findUnique({
where: { id },
select: {
id: true,
firstName: true,
lastName: true,
email: true,
},
});

// DO: Use transactions for related operations
await db.$transaction(async (tx) => {
const reservation = await tx.reservation.create({ data: reservationData });
await tx.folio.create({ data: { reservationId: reservation.id } });
return reservation;
});

// DON'T: N+1 queries
for (const reservation of reservations) {
const guest = await db.guest.findUnique({ where: { id: reservation.guestId } });
}

// DO: Use includes or joins
const reservations = await db.reservation.findMany({
include: { guest: true },
});

Logging​

Log Levels​

// Error: Unexpected errors, requires attention
logger.error('Failed to process payment', { error, reservationId });

// Warn: Potential issues, degraded functionality
logger.warn('Rate limit approaching', { remaining: 10 });

// Info: Important business events
logger.info('Reservation created', { reservationId, guestId });

// Debug: Detailed information for debugging
logger.debug('Processing request', { params, headers });

Structured Logging​

// DO: Use structured logging
logger.info('Guest checked in', {
reservationId: 'res_123',
guestId: 'guest_456',
roomNumber: '405',
timestamp: new Date().toISOString(),
});

// DON'T: String concatenation
logger.info(`Guest ${guestId} checked in to room ${roomNumber}`);

Security​

Input Validation​

// Always validate input
const schema = z.object({
email: z.string().email(),
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/),
amount: z.number().positive(),
});

const data = schema.parse(req.body);

Sensitive Data​

// DON'T: Log sensitive data
logger.info('User data', { user }); // May contain password

// DO: Sanitize before logging
logger.info('User authenticated', { userId: user.id, email: user.email });

// DON'T: Return sensitive data
res.json({ user }); // May contain password hash

// DO: Select specific fields
res.json({ user: { id, name, email } });

Comments​

// DO: Document complex logic
/**
* Calculates the total rate for a reservation.
* Applies seasonal adjustments and promotional discounts.
*
* @param reservation - The reservation details
* @param rates - Available rates for the period
* @returns The calculated total amount
*/
function calculateTotalRate(
reservation: Reservation,
rates: Rate[],
): number {
// Complex calculation logic
}

// DON'T: State the obvious
// Increment counter
counter++;

// DO: Explain why, not what
// Skip weekends as rate adjustments don't apply
if (isWeekend(date)) {
continue;
}