My App
CourseKitExamples

Full-Stack Setup

Combined backend and frontend integration guide

Overview

This guide shows how to wire up @hfu.digital/coursekit-nestjs and @hfu.digital/coursekit-react into a full-stack application. The backend exposes a REST API, and the frontend consumes it through the hooks.

Project Structure

my-app/
├── backend/
│   ├── prisma/
│   │   └── schema.prisma
│   ├── src/
│   │   ├── app.module.ts
│   │   ├── schedule.controller.ts
│   │   ├── event.controller.ts
│   │   ├── conflict.controller.ts
│   │   └── main.ts
│   └── package.json
├── frontend/
│   ├── src/
│   │   ├── App.tsx
│   │   ├── SchedulePage.tsx
│   │   └── EventCreator.tsx
│   └── package.json

Step 1: Backend Setup

Install dependencies:

cd backend
bun add @hfu.digital/coursekit-nestjs @nestjs/common @nestjs/core @nestjs/event-emitter rxjs class-validator class-transformer @prisma/client
bun add -d prisma

Add the CourseKit Prisma models to your schema (see Getting Started).

Generate Prisma client and run migrations:

bunx prisma generate
bunx prisma migrate dev --name init

Set up the module:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import {
    CourseKitModule,
    PrismaTimetableEventAdapter,
    PrismaRoomAdapter,
    PrismaInstructorAdapter,
    PrismaGroupAdapter,
    PrismaAvailabilityAdapter,
    PrismaAcademicPeriodAdapter,
    PrismaCourseAdapter,
} from '@hfu.digital/coursekit-nestjs';

const prisma = new PrismaClient();

@Module({
    imports: [
        CourseKitModule.register({
            eventStorage: new PrismaTimetableEventAdapter(
                prisma.timetableEvent,
                prisma.eventException,
                prisma.eventInstructor,
                prisma.eventGroup,
            ),
            roomStorage: new PrismaRoomAdapter(prisma.room),
            instructorStorage: new PrismaInstructorAdapter(prisma.instructor),
            groupStorage: new PrismaGroupAdapter(prisma.group, prisma.studentGroup),
            availabilityStorage: new PrismaAvailabilityAdapter(prisma.availability),
            periodStorage: new PrismaAcademicPeriodAdapter(prisma.academicPeriod),
            courseStorage: new PrismaCourseAdapter(prisma.course),
        }),
    ],
    controllers: [/* your controllers */],
})
export class AppModule {}

Step 2: API Endpoints

The frontend hooks expect these API endpoints:

HookMethodEndpointDescription
useTimetableGET/scheduleQuery materialized occurrences
useAvailabilityGET/availabilityGet entity availability
useConflictCheckPOST/conflicts/checkDry-run conflict check
useMutationPOST/eventsCreate event
useMutationPATCH/events/:idUpdate event
useMutationDELETE/events/:idDelete event
useMutationPOST/exceptionsCreate exception
useMutationDELETE/exceptions/:idDelete exception
useRoomSearchGET/roomsSearch rooms

See the NestJS REST API example for complete controller implementations.

Step 3: Frontend Setup

Install dependencies:

cd frontend
bun add @hfu.digital/coursekit-react react react-dom

Set up the provider:

// src/App.tsx
import { CourseKitProvider } from '@hfu.digital/coursekit-react';

function App() {
    return (
        <CourseKitProvider apiUrl="http://localhost:3000/coursekit">
            <SchedulePage />
        </CourseKitProvider>
    );
}

See the React Schedule App example for complete component implementations.

Step 4: Authentication

If your API requires authentication, pass a custom fetch function:

<CourseKitProvider
    apiUrl="http://localhost:3000/coursekit"
    fetch={(url, init) => fetch(url, {
        ...init,
        headers: {
            ...init?.headers,
            Authorization: `Bearer ${getAuthToken()}`,
        },
    })}
>
    <App />
</CourseKitProvider>

Step 5: CORS Configuration

If the frontend and backend run on different ports, configure CORS in NestJS:

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    app.enableCors({
        origin: 'http://localhost:5173', // Vite default port
        credentials: true,
    });
    await app.listen(3000);
}
bootstrap();

Development Workflow

# Terminal 1: Backend
cd backend && bun run start:dev

# Terminal 2: Frontend
cd frontend && bun run dev

Testing

Use the in-memory adapters for integration tests:

import { Test } from '@nestjs/testing';
import { CourseKitModule } from '@hfu.digital/coursekit-nestjs';
import {
    InMemoryTimetableEventStorage,
    InMemoryRoomStorage,
    InMemoryInstructorStorage,
    InMemoryGroupStorage,
    InMemoryAvailabilityStorage,
    InMemoryAcademicPeriodStorage,
    InMemoryCourseStorage,
} from '@hfu.digital/coursekit-nestjs/testing';

const module = await Test.createTestingModule({
    imports: [
        CourseKitModule.register({
            eventStorage: new InMemoryTimetableEventStorage(),
            roomStorage: new InMemoryRoomStorage(),
            instructorStorage: new InMemoryInstructorStorage(),
            groupStorage: new InMemoryGroupStorage(),
            availabilityStorage: new InMemoryAvailabilityStorage(),
            periodStorage: new InMemoryAcademicPeriodStorage(),
            courseStorage: new InMemoryCourseStorage(),
        }),
    ],
    controllers: [ScheduleController, EventController, ConflictController],
}).compile();

const app = module.createNestApplication();
await app.init();

// Use supertest or similar to test endpoints

Next Steps

On this page