HFU Digital Docs
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

app.module.ts
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

booking.controller.ts
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

availability.controller.ts
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

recurrence.controller.ts
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

On this page