← Dashboard | Capstone Project
Week 7–8
🚀 Week 7–8 · Final Project

Capstone Project

Build a full-stack AI-powered web application, containerise it with Docker, and deploy it on Azure Kubernetes Service. This is your showcase piece for job interviews.

Spring Boot React + TypeScript LangChain4j Docker Azure Kubernetes PostgreSQL GitHub Actions

1 · What You Will Build — SmartTask AI

🧠 The Big Picture Think of it like a personal to-do app, but smarter. You type a task like "prepare for technical interview" and the AI suggests sub-tasks, estimates effort, and even recommends resources — just like a senior developer would advise a fresher.

Core Features

FeatureWhat It DoesTechnologies Used
User AuthRegister + Login with JWT tokensSpring Security, JWT
Task CRUDCreate, read, update, delete tasksSpring Boot REST API
AI SuggestionsBreak any task into smaller steps using AILangChain4j + OpenAI / Azure OpenAI
DashboardView all tasks with filters and statusReact + TypeScript
Cloud DeployPublicly accessible URL on AzureDocker + AKS
CI/CD PipelineAuto-build + deploy on every git pushGitHub Actions
✅ Why this project works for interviews It touches every technology in the course: Java backend, REST API, JPA + PostgreSQL, React frontend, AI integration, Docker, Kubernetes, CI/CD. When an interviewer asks "show me something you built," this is your answer.

2 · System Architecture

The system has three main layers: a React frontend, a Spring Boot backend API, and an AI service. Everything runs as Docker containers orchestrated by Kubernetes.

High-Level Diagram

🌐 Browser (User)
↓ HTTPS
🔀 NGINX Ingress Controller (AKS)
↙ /api/*
↘ /*
🌱 Spring Boot API
Port 8080 · 2 replicas
⚛️ React Frontend
NGINX · Port 80 · 1 replica
↓ JDBC           ↓ OpenAI API
🐘 PostgreSQL
Azure DB or K8s Pod
🤖 OpenAI / Azure OpenAI
External API (gpt-4o-mini)

Data Flow — "AI Suggest Sub-tasks"

  1. User types task title → clicks "Get AI Suggestions"
  2. React sends POST /api/tasks/{id}/suggest to Spring Boot
  3. Spring Boot calls LangChain4j with the task title + a system prompt
  4. LangChain4j sends request to OpenAI GPT-4o-mini
  5. AI returns a list of sub-tasks as JSON
  6. Spring Boot saves sub-tasks to PostgreSQL, returns them to React
  7. React displays them as a checklist under the task

3 · Week-by-Week Delivery Plan

📅 You have 2 weeks (Week 7 + Week 8) Each week has a clear goal and a demo checkpoint. This structure is exactly how real software teams work in sprints. Follow it strictly — don't jump ahead.
Week 7 · Day 1–2
Project Setup
  • Create GitHub repo
  • Spring Boot init (Initializr)
  • React + Vite + TypeScript
  • docker-compose with PostgreSQL
  • README with architecture diagram
Week 7 · Day 3–5
Core Backend API
  • User, Task, SubTask entities + JPA
  • CRUD endpoints (REST)
  • JWT auth (register / login)
  • Postman collection tested
  • Unit tests with JUnit
Week 8 · Day 1–3
Frontend + AI
  • React pages: Login, Dashboard, TaskDetail
  • Axios API calls with JWT header
  • LangChain4j integration + prompt
  • AI suggestions endpoint working
  • Tailwind / CSS styling
Week 8 · Day 4–5
Deploy + Polish
  • Dockerfiles for API + frontend
  • Push images to Azure Container Registry
  • AKS deployment YAML files
  • GitHub Actions CI/CD pipeline
  • Live demo rehearsal + portfolio update

4 · Full Tech Stack

LayerTechnologyWhy
Backend LanguageJava 21Industry standard for enterprise apps
Backend FrameworkSpring Boot 3.xAuto-configuration, massive ecosystem
Database ORMSpring Data JPA + HibernateWrite less SQL, use Java objects
DatabasePostgreSQL 16Production-grade relational DB
AuthenticationSpring Security + JWTStateless auth for REST APIs
AI LibraryLangChain4jSpring-native LLM integration
FrontendReact 18 + TypeScriptMost popular frontend stack today
Build Tool (FE)ViteFastest React dev server
HTTP Client (FE)AxiosSimple promise-based API calls
ContainerisationDocker + Docker ComposeConsistent local + prod environment
OrchestrationAzure Kubernetes Service (AKS)Cloud-managed K8s cluster
Container RegistryAzure Container Registry (ACR)Store Docker images in Azure
CI/CDGitHub ActionsFree, integrated with GitHub
Version ControlGit + GitHubIndustry standard

5 · Backend — Spring Boot API

Project Structure

smarttask-api/
├── src/main/java/com/campustoai/smarttask/
│   ├── SmartTaskApplication.java          // @SpringBootApplication
│   ├── config/
│   │   ├── SecurityConfig.java            // JWT filter, CORS config
│   │   └── AiConfig.java                  // LangChain4j bean setup
│   ├── model/
│   │   ├── User.java                      // @Entity
│   │   ├── Task.java                      // @Entity, @ManyToOne User
│   │   └── SubTask.java                   // @Entity, @ManyToOne Task
│   ├── repository/
│   │   ├── UserRepository.java
│   │   ├── TaskRepository.java
│   │   └── SubTaskRepository.java
│   ├── service/
│   │   ├── AuthService.java
│   │   ├── TaskService.java
│   │   └── AiSuggestionService.java       // Calls LangChain4j
│   └── controller/
│       ├── AuthController.java            // POST /api/auth/register, /login
│       ├── TaskController.java            // CRUD /api/tasks
│       └── AiController.java              // POST /api/tasks/{id}/suggest
└── src/main/resources/
    └── application.properties

Task Entity

@Entity
@Table(name = "tasks")
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String title;

    private String description;

    @Enumerated(EnumType.STRING)
    private TaskStatus status = TaskStatus.TODO;  // TODO, IN_PROGRESS, DONE

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @OneToMany(mappedBy = "task", cascade = CascadeType.ALL)
    private List<SubTask> subTasks = new ArrayList<>();

    @CreationTimestamp
    private LocalDateTime createdAt;

    // Getters + Setters (or use Lombok @Data)
}

Task Controller

@RestController
@RequestMapping("/api/tasks")
public class TaskController {

    private final TaskService taskService;

    public TaskController(TaskService taskService) {
        this.taskService = taskService;
    }

    @GetMapping
    public List<TaskDTO> getAllTasks(@AuthenticationPrincipal UserDetails user) {
        return taskService.getTasksForUser(user.getUsername());
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public TaskDTO createTask(@RequestBody CreateTaskRequest req,
                               @AuthenticationPrincipal UserDetails user) {
        return taskService.createTask(req, user.getUsername());
    }

    @PutMapping("/{id}")
    public TaskDTO updateTask(@PathVariable Long id,
                               @RequestBody UpdateTaskRequest req) {
        return taskService.updateTask(id, req);
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteTask(@PathVariable Long id) {
        taskService.deleteTask(id);
    }
}

6 · Frontend — React + TypeScript

Project Structure

smarttask-ui/
├── src/
│   ├── api/
│   │   └── axiosInstance.ts      // Base URL + JWT header interceptor
│   ├── pages/
│   │   ├── LoginPage.tsx
│   │   ├── DashboardPage.tsx     // List of all tasks
│   │   └── TaskDetailPage.tsx    // Single task + AI suggestions
│   ├── components/
│   │   ├── TaskCard.tsx
│   │   ├── SubTaskList.tsx
│   │   └── Navbar.tsx
│   ├── types/
│   │   └── index.ts              // Task, SubTask, User interfaces
│   └── App.tsx                   // React Router routes
├── Dockerfile                    // Build + serve with NGINX
└── vite.config.ts

Axios Instance with JWT Header

// src/api/axiosInstance.ts
import axios from 'axios';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL ?? 'http://localhost:8080',
});

// Automatically attach the JWT token to every request
api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

export default api;

Dashboard Page — Fetch All Tasks

// src/pages/DashboardPage.tsx
import { useEffect, useState } from 'react';
import api from '../api/axiosInstance';
import { Task } from '../types';

export default function DashboardPage() {
  const [tasks, setTasks] = useState<Task[]>([]);

  useEffect(() => {
    api.get<Task[]>('/api/tasks')
       .then(res => setTasks(res.data))
       .catch(err => console.error(err));
  }, []);

  return (
    <div className="dashboard">
      <h1>My Tasks</h1>
      {tasks.map(task => (
        <div key={task.id} className="task-card">
          <h3>{task.title}</h3>
          <span className={`status ${task.status.toLowerCase()}`}>{task.status}</span>
        </div>
      ))}
    </div>
  );
}

7 · AI Feature — Sub-task Suggestions

Spring Boot Side — AI Service

// AiSuggestionService.java
@Service
public class AiSuggestionService {

    private final TaskAssistant assistant;  // LangChain4j @AiService

    public List<String> suggestSubTasks(String taskTitle) {
        String response = assistant.breakDownTask(taskTitle);
        // Response is a numbered list like "1. Research topic\n2. Create outline\n..."
        return Arrays.stream(response.split("\\n"))
                     .filter(line -> !line.isBlank())
                     .map(line -> line.replaceAll("^\\d+\\.\\s*", ""))
                     .collect(Collectors.toList());
    }
}

LangChain4j AI Service Interface

// TaskAssistant.java
@AiService
public interface TaskAssistant {

    @SystemMessage("""
        You are a productivity coach helping software developers.
        When given a task title, break it into 5 clear, actionable sub-tasks.
        Return ONLY a numbered list. No extra text or explanation.
        """)
    String breakDownTask(@UserMessage String taskTitle);
}

LangChain4j Dependency (pom.xml)

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-spring-boot-starter</artifactId>
    <version>0.32.0</version>
</dependency>
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
    <version>0.32.0</version>
</dependency>

application.properties — AI Config

langchain4j.open-ai.chat-model.api-key=${OPENAI_API_KEY}
langchain4j.open-ai.chat-model.model-name=gpt-4o-mini
langchain4j.open-ai.chat-model.temperature=0.3
langchain4j.open-ai.chat-model.max-tokens=500
💡 Saving API costs Use gpt-4o-mini instead of gpt-4o — it's 10x cheaper and fast enough for simple task breakdowns. Set max-tokens=500 to cap cost per call.

8 · Deployment on Azure Kubernetes Service

Step 1 — Dockerfiles

# Backend Dockerfile (multi-stage)
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY . .
RUN ./mvnw clean package -DskipTests

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# Frontend Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

Step 2 — Push Images to ACR

# Login to Azure
az login
az acr login --name smarttaskacr

# Tag and push backend image
docker build -t smarttask-api ./smarttask-api
docker tag smarttask-api smarttaskacr.azurecr.io/smarttask-api:v1
docker push smarttaskacr.azurecr.io/smarttask-api:v1

# Tag and push frontend image
docker build -t smarttask-ui ./smarttask-ui
docker tag smarttask-ui smarttaskacr.azurecr.io/smarttask-ui:v1
docker push smarttaskacr.azurecr.io/smarttask-ui:v1

Step 3 — Kubernetes Deployment YAML

# k8s/api-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: smarttask-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: smarttask-api
  template:
    metadata:
      labels:
        app: smarttask-api
    spec:
      containers:
        - name: api
          image: smarttaskacr.azurecr.io/smarttask-api:v1
          ports:
            - containerPort: 8080
          env:
            - name: SPRING_DATASOURCE_URL
              valueFrom:
                secretKeyRef:
                  name: smarttask-secrets
                  key: db-url
            - name: OPENAI_API_KEY
              valueFrom:
                secretKeyRef:
                  name: smarttask-secrets
                  key: openai-key

Step 4 — GitHub Actions CI/CD

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Log in to Azure
        uses: azure/login@v2
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Build and push backend image
        run: |
          az acr build --registry smarttaskacr \
            --image smarttask-api:${{ github.sha }} \
            ./smarttask-api

      - name: Deploy to AKS
        uses: azure/k8s-deploy@v4
        with:
          resource-group: smarttask-rg
          name: smarttask-aks
          manifests: k8s/
          images: smarttaskacr.azurecr.io/smarttask-api:${{ github.sha }}
⚠️ Cost Warning AKS clusters cost money even when idle. After your demo, run az aks stop --name smarttask-aks --resource-group smarttask-rg to pause the cluster. Use the Azure Free Trial $200 credit wisely.

9 · Submission Checklist

Before your final demo, verify every item below:

  • GitHub repo is public with a detailed README (screenshots, architecture diagram, how to run locally)
  • Application runs with docker compose up on a fresh machine
  • All 4 CRUD endpoints tested in Postman (collection exported + committed to repo)
  • JWT authentication works — cannot access tasks without a valid token
  • AI sub-task suggestion endpoint returns real results from OpenAI
  • React frontend connects to backend API (no CORS errors in browser console)
  • Docker images pushed to Azure Container Registry
  • Application deployed and accessible via public AKS IP or domain
  • GitHub Actions workflow runs green on push to main
  • At least 5 unit tests passing (mvn test shows green)
  • Secrets (API keys, DB passwords) are in environment variables — NOT hardcoded in code
  • LinkedIn post drafted: project name, tech stack, live link, GitHub link

10 · Demo Guide & Portfolio Tips

5-Minute Demo Script

TimeWhat to ShowWhat to Say
0:00 – 0:30Open live app URL in browser"This is SmartTask AI — a full-stack app I built and deployed on Azure Kubernetes."
0:30 – 1:30Register new user, login, create a task"The backend is Spring Boot with JWT authentication. User credentials are stored securely hashed with BCrypt."
1:30 – 2:30Click "Get AI Suggestions""Here I integrated OpenAI via LangChain4j. The AI breaks down any task into actionable sub-steps."
2:30 – 3:30Show GitHub Actions pipeline (green)"Every push to main automatically builds Docker images and deploys to AKS. That's CI/CD in action."
3:30 – 5:00Show architecture diagram in README"The architecture uses React frontend, Spring Boot API, PostgreSQL, all containerised. Walk through each layer."

How to Add This to Your Resume

✅ Resume bullet point template

SmartTask AI — Full-Stack Developer (Personal Project, Month Year)
• Built a task management web app with AI-powered sub-task suggestions using Spring Boot, React, LangChain4j, and GPT-4o-mini
• Designed REST API with JWT auth, Spring Data JPA, and PostgreSQL; deployed on Azure Kubernetes Service
• Set up CI/CD pipeline with GitHub Actions, reducing manual deployment effort by 100%
Tech Stack: Java 21, Spring Boot 3, React 18, TypeScript, Docker, AKS, LangChain4j, PostgreSQL

LinkedIn Post Template

📢 Share Your Project

"Excited to share my capstone project — SmartTask AI! 🚀

Over the last 2 weeks I built a full-stack AI-powered task manager using:
✅ Spring Boot + JWT authentication
✅ React + TypeScript frontend
✅ LangChain4j + OpenAI GPT-4o-mini for AI suggestions
✅ Deployed on Azure Kubernetes with GitHub Actions CI/CD

Live link: [your-aks-ip]
GitHub: [your-repo-url]

#Java #SpringBoot #React #Azure #Kubernetes #AI #BuildInPublic"

Common Demo Pitfalls — Avoid These

MistakeFix
API key exposed in GitHub repoAlways use environment variables + add .env to .gitignore
App crashes during live demoTest the exact demo flow 3 times before the interview
Can't explain a technology you usedUnderstand every line of code you wrote — interviewers will dig deep
No READMEREADME is the first thing interviewers check — write it like a product launch page
Hardcoded credentials in DBUse Kubernetes Secrets for all sensitive config

★ Interview Q&A

1 Walk me through the architecture of your capstone project.
"My project SmartTask AI has three layers. The React TypeScript frontend makes API calls to a Spring Boot backend running on port 8080. The backend authenticates users with JWT, stores data in PostgreSQL using Spring Data JPA, and calls OpenAI via LangChain4j for AI suggestions. Everything runs as Docker containers on Azure Kubernetes Service. GitHub Actions builds and deploys the images on every push to main."
  • Always describe it from user's perspective outward to infrastructure
  • Mention how layers communicate (REST, JDBC, etc.)
  • Shows you understand the whole system, not just your part
2 Why did you choose Kubernetes over just running Docker directly?
"Docker alone is great locally, but in production you need self-healing, scaling, and zero-downtime deployments. If the API container crashes, Kubernetes automatically restarts it. I configured 2 replicas for the backend — if one pod fails, traffic automatically goes to the other. Rolling updates let me deploy new code without any downtime."
  • Key point: Kubernetes solves problems that Docker Compose alone can't handle in production
3 How did you handle secrets like the OpenAI API key in Kubernetes?
"I used Kubernetes Secrets to store the OpenAI API key and database password. In the deployment YAML, I reference the secret using secretKeyRef — the pod gets the value as an environment variable at runtime. Secrets are base64-encoded in etcd. For extra security in a real team environment, I'd integrate Azure Key Vault."
  • Never hardcode secrets in code or Docker images — instant red flag in interviews
  • Bonus: mention that base64 is encoding, not encryption — show deeper understanding
4 How does JWT authentication work in your Spring Boot app?
"When a user logs in, the server validates their credentials and signs a JWT token with a secret key. The token contains the user's email and expiry time. The client stores this in localStorage and sends it in the Authorization header as 'Bearer <token>' with every request. Spring Security's filter chain intercepts each request, validates the token signature, and extracts the user identity — no database call needed for auth."
  • Emphasize: JWT is stateless — server doesn't store sessions
  • Know the difference: Authentication (who are you?) vs Authorization (what can you do?)
5 What would you improve if you had more time?
This is a self-awareness question. Good answers show you can critically evaluate your own work:
  • "I would add WebSocket support so AI suggestions stream in word by word (like ChatGPT) instead of waiting for the full response"
  • "I would add Redis caching so repeated AI suggestions for the same task title don't cost extra API calls"
  • "I would implement refresh tokens to improve security — currently JWT tokens expire and users get logged out"
  • "I would write integration tests using Testcontainers to test the real database, not an in-memory one"
Avoid saying: "I wouldn't change anything." That shows you can't self-critique.
6 What challenges did you face and how did you solve them?
Interviewers want to hear about real problems — not "it all went smoothly." Good examples:
  • CORS errors: React on port 5173 calling Spring Boot on 8080 — fixed by adding @CrossOrigin or a global CORS config in SecurityConfig
  • JWT in Docker: Tokens signed with a hardcoded secret — moved to environment variable so it's consistent across containers
  • AI response parsing: OpenAI sometimes returned a numbered list with extra whitespace — added regex cleaning in the service layer
  • K8s image pull errors: AKS couldn't pull from ACR — fixed by attaching the ACR to the AKS cluster with az aks update --attach-acr