Back to Projects
React 19TypeScriptViteSpring Boot 3Java 21Spring Data JPAFlywayMySQL 8JWTGemini EmbeddingsFirebase Auth

SkillEx

An Advanced OOP course project taken past the coursework brief. The interesting constraint is that there is no currency: a match is only useful when two people's offers and wants genuinely complement each other, which turns matching into the core engineering problem rather than a feature bolted on later.

Full-Stack Developer
SkillEx

The Problem

Skill learning is gated by cost, while plenty of people hold knowledge worth trading. Keyword matching is not good enough to pair them — someone offering "React" and someone wanting "frontend development" never meet, even though they should.

The Solution

Skills are embedded through the Gemini Embedding API and matched by semantic similarity, so related phrasings converge. Because a hosted embedding service is a hard dependency for a course project, the backend falls back to local embeddings whenever no API key is present — the feature degrades in quality rather than disappearing.

System Architecture

A monorepo with clean seams. The frontend is organised as feature-first vertical slices, and all network access funnels through a single ApiClient singleton, so JWT attachment and error shape live in one place. The backend keeps controllers thin and routes work through service interfaces to implementations, with Flyway owning the schema as versioned SQL.

SkillEx request path: a React feature slice calls through a single API client into a layered Spring Boot backend.CLIENT - REACT 19 + VITEFeature Slicevertical, per-domainFeature ServiceauthService.login()ApiClientsingleton + JWT headerHTTP JSON, proxied by Vite in devEDGEJwtAuthFiltertoken verificationCORS + Error HandlerSpring Security configverified requestAPPLICATION - SPRING BOOT 3 / JAVA 21RestControllerrouting onlyService InterfaceabstractionServiceImplbusiness logicSpring Data JPAPERSISTENCEJPA Repositoryquery derivationMySQL 8.0Flyway-versioned schemaGemini Embedding APIfalls back to local vectors
SkillEx request path: a React feature slice calls through a single API client into a layered Spring Boot backend.Scroll sideways to see the full diagram

Key Features

Innovative solutions developed to solve complex technical challenges.

Semantic skill matching

Gemini embeddings place offered and wanted skills in the same vector space, so "React" and "frontend development" match on meaning rather than on shared characters.

Graceful embedding fallback

With EMBEDDING_PROVIDER set to api but no key present, the backend silently switches to local embeddings instead of failing the request.

Single API boundary

An ApiClient singleton wraps fetch, attaches the JWT, and normalises every response into ApiResponse<T> — so success, error, and message handling are identical across features.

Versioned schema with Flyway

Migrations run as ordered SQL on startup, so a fresh clone reaches the current schema without anyone running statements by hand.

Feature-first frontend slices

Each domain owns its components, services, and types together, so a feature can be read or removed without tracing through shared folders.

Challenges & Breakthroughs

1

Challenge: A hosted embedding API is a single point of failure for the one feature the product is built around.

Growth: Made the provider a configuration choice with an automatic local fallback rather than a hard dependency. Designing the degraded path first meant the feature had a defined behaviour without a key, instead of an exception.

2

Challenge: Getting a Spring Boot backend and a Vite frontend running together locally involves MySQL, ports, and migration order.

Growth: Automated it — a pre-start script detects and starts MySQL, waits for port 3306, and ensures the database exists before the backend boots. Setup friction is a real cost on a team project; removing it is worth the scripting.

3

Challenge: Thin controllers are easy to describe and easy to erode once deadlines arrive.

Growth: Keeping the service as an interface with a separate implementation made the boundary structural rather than a convention. It is harder to leak business logic into a controller when doing so means bypassing a type you would have to go around deliberately.

Results & Impact

Semantic
Matching
Gemini embeddings, with an automatic local fallback
Monorepo
Stack
React 19 frontend and Spring Boot 3 backend sharing one migration source
Flyway
Schema
Versioned SQL applied automatically on first run

Next Project

ConvoVerse