Coding Standards
This document outlines the coding standards and best practices for HMP development.
TypeScript Guidelines
General Principles
- Use TypeScript strict mode
- Prefer
constoverlet, avoidvar - 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;
}