RoomKitExamples
NestJS API
Build a complete room booking API with NestJS and RoomKit
Overview
This example shows how to build a full-featured room booking REST API using NestJS and @hfu.digital/roomkit-nestjs.
Module Setup
import { Module } from '@nestjs/common';
import { RoomKitModule, PrismaRoomKitAdapter } from '@hfu.digital/roomkit-nestjs';
import { PrismaClient } from '@prisma/client';
import { BookingController } from './booking.controller';
import { RoomController } from './room.controller';
import { LocationController } from './location.controller';
import { AdminController } from './admin.controller';
const prisma = new PrismaClient();
@Module({
imports: [
RoomKitModule.register({
storage: new PrismaRoomKitAdapter(prisma),
priorities: [
{ name: 'EXAM', weight: 200 },
{ name: 'LECTURE', weight: 100 },
{ name: 'SEMINAR', weight: 75 },
{ name: 'LAB', weight: 60 },
{ name: 'STUDY_GROUP', weight: 50 },
{ name: 'OPEN', weight: 25 },
],
features: {
exams: true,
bulkOperations: true,
},
travelTimeMatrix: {
'campus-a|campus-b': 30,
'campus-a|campus-c': 45,
'campus-b|campus-c': 20,
},
events: {
BookingRequested: (event) => {
console.log(`New booking: ${event.payload.title}`);
},
ConflictDetected: (event) => {
console.log(`Conflict: ${event.payload.bookingAId} vs ${event.payload.bookingBId}`);
},
BlackoutImpactDetected: (event) => {
console.log(`Blackout impacts ${event.payload.impactedBookings.length} bookings`);
},
},
}),
],
controllers: [BookingController, RoomController, LocationController, AdminController],
})
export class AppModule {}Booking Controller
import {
Controller, Get, Post, Put, Body, Param, Query,
} from '@nestjs/common';
import {
BookingService,
AvailabilityService,
ConflictService,
type AvailabilityFilter,
} from '@hfu.digital/roomkit-nestjs';
@Controller('bookings')
export class BookingController {
constructor(
private readonly bookings: BookingService,
private readonly availability: AvailabilityService,
private readonly conflicts: ConflictService,
) {}
@Post()
async create(@Body() body: {
roomId: string;
requesterId: string;
title: string;
startsAt: string;
endsAt: string;
purposeType: string;
onBehalfOfId?: string;
description?: string;
metadata?: string;
}) {
return this.bookings.create({
...body,
startsAt: new Date(body.startsAt),
endsAt: new Date(body.endsAt),
});
}
@Get(':id')
async getById(@Param('id') id: string) {
return this.bookings.getById(id);
}
@Put(':id')
async modify(
@Param('id') id: string,
@Body() body: {
title?: string;
startsAt?: string;
endsAt?: string;
roomId?: string;
version: number;
},
) {
const { version, startsAt, endsAt, ...rest } = body;
return this.bookings.modify(
id,
{
...rest,
...(startsAt && { startsAt: new Date(startsAt) }),
...(endsAt && { endsAt: new Date(endsAt) }),
},
version,
);
}
@Post(':id/confirm')
async confirm(@Param('id') id: string, @Body('triggeredBy') triggeredBy: string) {
return this.bookings.confirm(id, triggeredBy);
}
@Post(':id/check-in')
async checkIn(@Param('id') id: string, @Body('triggeredBy') triggeredBy: string) {
return this.bookings.checkIn(id, triggeredBy);
}
@Post(':id/complete')
async complete(@Param('id') id: string, @Body('triggeredBy') triggeredBy: string) {
return this.bookings.complete(id, triggeredBy);
}
@Post(':id/cancel')
async cancel(
@Param('id') id: string,
@Body() body: { triggeredBy: string; reason?: string },
) {
return this.bookings.cancel(id, body.triggeredBy, body.reason);
}
@Get()
async listByRoom(
@Query('roomId') roomId: string,
@Query('startsAt') startsAt: string,
@Query('endsAt') endsAt: string,
@Query('limit') limit?: string,
@Query('cursor') cursor?: string,
) {
return this.bookings.getByRoom(
roomId,
{ startsAt: new Date(startsAt), endsAt: new Date(endsAt) },
{ limit: limit ? parseInt(limit, 10) : 20, cursor },
);
}
}Availability Controller
import { Controller, Get, Query } from '@nestjs/common';
import { AvailabilityService } from '@hfu.digital/roomkit-nestjs';
@Controller('availability')
export class AvailabilityController {
constructor(private readonly availability: AvailabilityService) {}
@Get()
async search(
@Query('startsAt') startsAt: string,
@Query('endsAt') endsAt: string,
@Query('minCapacity') minCapacity?: string,
@Query('capacityType') capacityType?: 'seated' | 'exam' | 'standing',
@Query('requiredEquipment') requiredEquipment?: string,
@Query('locationScope') locationScope?: string,
@Query('limit') limit?: string,
@Query('cursor') cursor?: string,
) {
return this.availability.search(
{
timeRange: {
startsAt: new Date(startsAt),
endsAt: new Date(endsAt),
},
minCapacity: minCapacity ? parseInt(minCapacity, 10) : undefined,
capacityType,
requiredEquipment: requiredEquipment?.split(','),
locationScope,
},
{
limit: limit ? parseInt(limit, 10) : 20,
cursor,
},
);
}
}Recurrence Controller
import { Controller, Post, Body, Put, Param } from '@nestjs/common';
import { RecurrenceService } from '@hfu.digital/roomkit-nestjs';
@Controller('recurrence')
export class RecurrenceController {
constructor(private readonly recurrence: RecurrenceService) {}
@Post('series')
async createSeries(@Body() body: {
booking: {
roomId: string;
requesterId: string;
title: string;
startsAt: string;
endsAt: string;
purposeType: string;
};
rule: {
frequency: 'weekly' | 'biweekly' | 'custom';
daysOfWeek: number[];
calendarWeeks?: number[];
seriesStartsAt: string;
seriesEndsAt: string;
exceptionDates?: string[];
};
}) {
return this.recurrence.createRecurringBooking(
{
...body.booking,
startsAt: new Date(body.booking.startsAt),
endsAt: new Date(body.booking.endsAt),
},
{
...body.rule,
seriesStartsAt: new Date(body.rule.seriesStartsAt),
seriesEndsAt: new Date(body.rule.seriesEndsAt),
exceptionDates: (body.rule.exceptionDates ?? []).map((d) => new Date(d)),
calendarWeeks: body.rule.calendarWeeks ?? null,
},
);
}
@Put(':bookingId/single')
async modifySingle(
@Param('bookingId') bookingId: string,
@Body() body: { changes: Record<string, unknown>; version: number },
) {
return this.recurrence.modifySingle(bookingId, body.changes, body.version);
}
}Running the Example
# Clone and install
git clone https://github.com/hfu-digital/RoomKit.git
cd RoomKit/examples/nestjs-api
bun install
# Set up the database
cp .env.example .env
bunx prisma migrate dev
# Start the server
bun run start:dev