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.jsonStep 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 prismaAdd the CourseKit Prisma models to your schema (see Getting Started).
Generate Prisma client and run migrations:
bunx prisma generate
bunx prisma migrate dev --name initSet 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:
| Hook | Method | Endpoint | Description |
|---|---|---|---|
useTimetable | GET | /schedule | Query materialized occurrences |
useAvailability | GET | /availability | Get entity availability |
useConflictCheck | POST | /conflicts/check | Dry-run conflict check |
useMutation | POST | /events | Create event |
useMutation | PATCH | /events/:id | Update event |
useMutation | DELETE | /events/:id | Delete event |
useMutation | POST | /exceptions | Create exception |
useMutation | DELETE | /exceptions/:id | Delete exception |
useRoomSearch | GET | /rooms | Search 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-domSet 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 devTesting
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 endpointsNext Steps
- Add custom constraints for your institution's rules: Custom Constraints
- Set up academic scheduling patterns: RRULE Patterns
- Use a different ORM: Custom Storage Adapter