KTH-Course-Community is a full-stack application designed to help KTH students search for and explore courses. It features a Next.js frontend and a NestJS backend, powered by ElasticSearch for searching and PostgreSQL for data storage.
All contributions to the project are very welcome! To make a contribution:
- Open a new issue
- Usually good to await comment from code admins before starting working on the feature.
- Create a new branch or fork
- Implement new feature / ticket
- Create a PR into the Dev branch
- Link issue in PR.
- Wait for approval or comment by code admins
If you have any suggestions you are always welcome to open an issue in the repository!
Before you begin, ensure you have the following installed on your local machine:
- Node.js (v18 or later recommended)
- npm (comes with Node.js)
- Docker Desktop
Follow these steps to get the project up and running on your local machine.
git clone https://github.com/kthaisociety/KTH-Course-Community.git
cd KTH-Course-CommunityInstall all the necessary dependencies for both the frontend and backend from the root directory.
npm iYou'll need to create two .env files, one for the backend and one for the frontend.
Backend (backend-nest/.env)
Create a file at backend-nest/.env and add the following variables.
# PostgreSQL database connection string
DATABASE_URL=postgresql://user:password@host:port/database
# ElasticSearch credentials
ELASTICSEARCH_URL=http://localhost:9200
ELASTICSEARCH_USERNAME=elastic
ELASTICSEARCH_PASSWORD= # The password you get after starting ElasticSearch
# SuperTokens Authentication
ST_CONNECTION_URI=https://try.supertokens.com
ST_API_KEY=
# Application URLs and Port
PORT=8080
WEBSITE_DOMAIN=http://localhost:3000Frontend (frontend/.env)
Create a file at frontend/.env and add the following variables.
NEXT_PUBLIC_BACKEND_DOMAIN=http://localhost:8080
NEXT_PUBLIC_WEBSITE_DOMAIN=http://localhost:3000This project uses PostgreSQL and drizzle-orm. Make sure you have a running PostgreSQL instance and that the DATABASE_URL in backend-nest/.env is configured correctly.
Once configured, run the database migrations to set up the schema:
cd backend-nest
npm run db:generate
npm run db:pushYou can run a local instance of ElasticSearch using Docker. The following command will download and start it.
curl -fsSL https://elastic.co/start-local | shWhen the process has finished, it will print a password for the elastic user. Make sure to copy this password and add it to the ELASTICSEARCH_PASSWORD variable in your backend-nest/.env file.
You can now start the backend and frontend servers.
Start the Backend
npm run dev:beStart the Frontend
npm run dev:feThe frontend will be available at http://localhost:3000.
After the backend has started, you can ingest course data into ElasticSearch by running the following command:
curl -X POST "http://localhost:8080/ingest/courses"This process may take some time. You can monitor the logs from the backend server for progress.
To build the Docker image, run
docker build -t your-dockerhub-username/course-compass-frontend:latest -f Dockerfile.frontend .
docker build -t your-dockerhub-username/course-compass-backend:latest -f Dockerfile.backend .This app uses the Vercel AI SDK in both workspaces:
backend-nestusesaifor the agent, tool calling, streaming responses, and embeddings.frontenduses@ai-sdk/reactandaifor chat transport, typed UI messages, and rendering tool results.
Add AI_GATEWAY_API_KEY to backend-nest/.env.local:
AI_GATEWAY_API_KEY=your_key_hereGet a key at vercel.com/dashboard → AI Gateway → API Keys.
The frontend does not need a model provider key. It only needs NEXT_PUBLIC_BACKEND_DOMAIN, which is already part of the frontend setup above.
Browser (/ai-demo, useChat + DefaultChatTransport)
→ POST <NEXT_PUBLIC_BACKEND_DOMAIN>/ai/chat
→ NestJS AiController
→ kthCourseAgent (ToolLoopAgent)
→ retrieveKthCourses / getWeather
→ AI Gateway → openai/gpt-5.4-mini
← UI message stream
The demo currently sends requests directly from the browser to the NestJS backend. A Next.js proxy route also exists at frontend/app/api/ai/chat/route.ts if you want to switch to a same-origin /api/ai/chat path later.
| File | Role |
|---|---|
backend-nest/src/ai/ai.controller.ts |
Exposes POST /ai/chat, validates locale and preferredDifficulty, then streams the agent response with pipeAgentUIStreamToResponse |
backend-nest/src/ai/kth-course-agent.ts |
Defines the ToolLoopAgent, the model (openai/gpt-5.4-mini), base instructions, call options schema, prepareCall logic, and first-step tool routing |
backend-nest/src/ai/tools.ts |
Defines AI SDK tool(...) handlers for retrieveKthCourses and getWeather |
backend-nest/src/ai/ai.service.ts |
Provides reusable AI SDK embedding helpers via embed, embedMany, and cosineSimilarity using gateway.embeddingModel(...) |
The backend is where the actual model call happens. The current chat request body is:
{
"messages": [],
"locale": "en",
"preferredDifficulty": "beginner"
}locale and preferredDifficulty are optional. They are parsed by kthCourseAgentCallOptionsSchema and injected into the agent instructions in prepareCall(...).
| File | Role |
|---|---|
frontend/app/(public)/ai-demo/page.tsx |
Demo chat page at /ai-demo, uses useChat<KthCourseAgentUIMessage>() with DefaultChatTransport |
frontend/types/ai/kth-course-agent.ts |
Mirrors the backend tool input/output types so tool parts are strongly typed in the UI |
frontend/app/api/ai/chat/route.ts |
Optional proxy route that forwards the request to the backend and preserves the AI SDK data stream headers |
The demo page renders AI SDK message parts directly:
textparts become normal assistant messages.tool-retrieveKthCoursesparts render tool input/output cards.tool-getWeatherparts render tool input/output cards.
The frontend currently does not choose the model. The active model is fixed on the backend in backend-nest/src/ai/kth-course-agent.ts.
Edit the model field in backend-nest/src/ai/kth-course-agent.ts:
export const kthCourseAgent = new ToolLoopAgent({
model: "openai/gpt-5.4-mini",
// ...
});List all available models:
curl -s https://ai-gateway.vercel.sh/v1/models | jq -r '.data[].id'With both servers running, open http://localhost:3000/ai-demo.
This repo also includes short root-level agent instruction files:
AGENTS.mdfor Codex/OpenAI-style agentsCLAUDE.mdfor Claude-oriented workflows
Both files are intentionally concise and point agents to the same core project facts: workspace layout, common commands, and where the AI SDK integration lives.
Repo-local agent skills live under .agents/skills/. Right now the repo includes:
.agents/skills/ai-sdk/SKILL.mdfor AI SDK-specific guidance used in this codebase
The following scripts are available to be run from the root directory:
| Script | Description |
|---|---|
npm run dev:fe |
Starts the frontend development server. |
npm run dev:be |
Starts the backend development server. |
npm run add:fe |
Adds a dependency to the frontend workspace. |
npm run add:be |
Adds a dependency to the backend workspace. |
npm run rm:fe |
Removes a dependency from the frontend workspace. |
npm run rm:be |
Removes a dependency from the backend workspace. |
Other scripts can be found in the package.json files within the frontend and backend-nest directories.