AI-powered voice interview practice platform with instant structured feedback.
CareerVoice is a full-stack web application that lets users practice job interviews through real-time voice conversations with an AI interviewer. After each session, the platform automatically generates a structured performance report scored across five professional categories.
Most interview preparation happens through reading — flashcards, Q&A lists, or watching YouTube videos. But real interviews happen out loud, under pressure, in real time. That gap between reading and speaking is where candidates fail.
CareerVoice bridges that gap by giving you a realistic, spoken interview experience with an AI that listens, responds naturally, and tells you exactly what to improve — available any time, with no scheduling required.
- You get honest, structured feedback that a friend or peer rarely gives
- Questions are tailored to your role, experience level, and tech stack — not generic
- You can repeat sessions as many times as needed without any friction
- The entire flow — setup, interview, feedback — takes under 15 minutes
- Real-time AI voice interviews — live spoken conversations using GPT-4, ElevenLabs voice, and Deepgram speech recognition, all orchestrated through Vapi AI
- Custom question generation — Google Gemini 2.0 Flash generates tailored questions based on your role, experience level, tech stack, and interview type (Technical / Behavioral / Mixed)
- Instant AI feedback — scored performance report across five categories: Communication Skills, Technical Knowledge, Problem Solving, Cultural Fit, and Confidence & Clarity
- Personal dashboard — full interview history with score tracking, type badges, and links to past feedback
- Community interview pool — browse and take interview sessions created by other users on the platform
- Secure authentication — email/password sign-up and sign-in using Firebase Auth with
httpOnlysession cookies - Rate limiting — all API routes are protected with an in-memory sliding-window rate limiter
- Structured logging — JSON-formatted logs on every API request with level, timestamp, and context
- CI/CD pipeline — GitHub Actions workflow that runs lint, tests, coverage, and build on every push
- Docker support — multi-stage production Docker build using a non-root user
| Category | Technology |
|---|---|
| Framework | Next.js 15 (App Router) |
| Language | TypeScript 5 |
| UI Library | React 19 |
| Styling | TailwindCSS 4, Radix UI |
| Voice Orchestration | Vapi AI (@vapi-ai/web) |
| AI Interviewer | OpenAI GPT-4 (via Vapi) |
| Text-to-Speech | ElevenLabs (via Vapi) |
| Speech-to-Text | Deepgram Nova-2 (via Vapi) |
| Feedback & Questions | Google Gemini 2.0 Flash (via Vercel AI SDK) |
| Database | Firebase Firestore |
| Authentication | Firebase Auth + Firebase Admin SDK |
| Schema Validation | Zod |
| Form Handling | React Hook Form + @hookform/resolvers |
| Notifications | Sonner |
| Date Formatting | dayjs |
| Icons | Lucide React |
| Testing | Jest 29, ts-jest, jest-environment-jsdom |
| Containerization | Docker (node:20-alpine, multi-stage) |
| CI/CD | GitHub Actions |
carrervoice/
│
├── app/ # Next.js App Router — all pages and API routes
│ ├── (auth)/ # Route group for unauthenticated pages
│ │ ├── layout.tsx # Auth layout wrapper
│ │ ├── sign-in/page.tsx # Sign-in page
│ │ └── sign-up/page.tsx # Sign-up page
│ │
│ ├── (root)/ # Route group for authenticated pages
│ │ ├── layout.tsx # Main app layout with navigation
│ │ ├── page.tsx # Dashboard — user history + community interviews
│ │ └── interview/
│ │ ├── page.tsx # Interview setup and configuration
│ │ └── [id]/
│ │ ├── page.tsx # Live voice interview session
│ │ └── feedback/
│ │ └── page.tsx # Post-interview feedback and scores
│ │
│ ├── api/ # REST API route handlers
│ │ ├── interviews/
│ │ │ ├── route.ts # GET — paginated interview list with filters
│ │ │ ├── [id]/route.ts # GET — single interview by ID
│ │ │ └── [id]/feedback/ # GET — feedback for a specific interview
│ │ │ └── route.ts
│ │ └── vapi/generate/
│ │ └── route.ts # POST — AI question generation via Gemini
│ │
│ ├── layout.tsx # Root layout — fonts, dark theme, toast provider
│ └── globals.css # Global styles and design tokens
│
├── components/ # Reusable UI components
│ ├── Agent.tsx # Core voice interview component and call state machine
│ ├── AuthForm.tsx # Shared sign-in / sign-up form
│ ├── InterviewCard.tsx # Dashboard card showing role, score, and feedback link
│ ├── DisplayTechIcons.tsx # Stacked tech stack icons fetched from devicons CDN
│ ├── FormField.tsx # Reusable form field wrapper
│ └── ui/ # Low-level UI primitives
│ ├── button.tsx
│ ├── form.tsx
│ ├── input.tsx
│ ├── label.tsx
│ └── sonner.tsx
│
├── firebase/
│ ├── admin.ts # Firebase Admin SDK — server-side only (Firestore + Auth)
│ └── client.ts # Firebase Client SDK — browser-side only (Auth UI)
│
├── lib/
│ ├── actions/
│ │ ├── auth.action.ts # Server Actions — signUp, signIn, signOut, getCurrentUser
│ │ └── general.action.ts # Server Actions — createFeedback, getInterviews, getFeedback
│ ├── apiResponse.ts # Typed success/error response factory for API routes
│ ├── logger.ts # Structured JSON logger (info, warn, error, request)
│ ├── rateLimit.ts # In-memory sliding-window rate limiter
│ ├── validation.ts # Zod schemas for API input validation
│ ├── vapi.sdk.ts # Vapi client singleton initialization
│ └── utils.ts # Utility functions — cn(), getTechLogos(), getRandomInterviewCover()
│
├── constants/
│ └── index.ts # Vapi assistant config, Zod feedback schema, tech name mappings
│
├── types/
│ ├── index.d.ts # Global TypeScript interfaces (User, Interview, Feedback, etc.)
│ └── vapi.d.ts # Vapi-specific type extensions
│
├── __tests__/ # Unit tests
│ ├── apiResponse.test.ts
│ ├── logger.test.ts
│ ├── rateLimit.test.ts
│ └── validation.test.ts
│
├── public/ # Static assets
│ ├── ai-avatar.png # AI interviewer avatar
│ ├── user-avatar.png # Default user avatar
│ ├── robot.png # Hero image
│ ├── covers/ # Company brand covers (12 images)
│ └── *.svg # Icons and decorative assets
│
├── .github/
│ └── workflows/
│ └── ci.yml # GitHub Actions — test and build pipeline
│
├── Dockerfile # Multi-stage Docker build
├── .dockerignore
├── next.config.ts
├── jest.config.ts
├── tsconfig.json
├── postcss.config.mjs
├── eslint.config.mjs
└── components.json
Make sure you have the following before starting:
- Node.js 20 or higher — Download here
- npm — comes bundled with Node.js
- Firebase project — Create one here with Firestore and Email/Password Authentication enabled
- Vapi AI account — Sign up here and create a voice workflow
- Google AI Studio API key — Get one here for Gemini access
git clone https://github.com/dharamdan01/carrervoice.git
cd carrervoicenpm installCreate a .env.local file in the root of the project:
touch .env.localAdd the following variables and fill in your values:
# ── Firebase Admin SDK (server-side — never expose these publicly) ──
FIREBASE_PROJECT_ID=your_project_id
FIREBASE_CLIENT_EMAIL=your_service_account_email
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
# ── Firebase Client SDK (browser-safe) ────────────────────────────
NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_project_id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_project.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id
# ── Vapi AI ────────────────────────────────────────────────────────
NEXT_PUBLIC_VAPI_WEB_TOKEN=your_vapi_public_token
NEXT_PUBLIC_VAPI_WORKFLOW_ID=your_vapi_workflow_id
# ── Google Gemini ──────────────────────────────────────────────────
GOOGLE_GENERATIVE_AI_API_KEY=your_gemini_api_keyWhere to find each key:
- Firebase Admin keys → Firebase Console → Project Settings → Service Accounts → Generate New Private Key
- Firebase Client keys → Firebase Console → Project Settings → General → Your Apps
- Vapi token → Vapi Dashboard → API Keys
- Gemini key → Google AI Studio → API Keys
npm run devOpen http://localhost:3000 in your browser.
# Development (with Turbopack)
npm run dev
# Production build
npm run build
npm run start
# Lint the codebase
npm run lint- Create an account at
/sign-upusing your email and password - Sign in at
/sign-in— a secure session cookie is issued automatically - Dashboard shows your past interviews and available community sessions
- Start an interview by clicking "Start an Interview" — configure your:
- Job role (e.g., Frontend Developer, Data Engineer)
- Experience level (Junior / Mid / Senior)
- Tech stack (e.g., React, Node.js, PostgreSQL)
- Interview type (Technical / Behavioral / Mixed)
- Number of questions (3 to 15)
- Click "Start Call" — the AI interviewer speaks to you and listens to your answers in real time
- Click "End Call" when done — feedback is generated automatically
- Review your feedback — scores, strengths, areas to improve, and a final assessment
- Revisit any past interview from the dashboard at any time
All endpoints require authentication via session cookie and include rate limiting, Zod input validation, and structured JSON logging.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/interviews |
Returns paginated list of user's interviews. Supports type, minScore, maxScore, page, and limit query params. Rate limit: 30 req/min. |
GET |
/api/interviews/[id] |
Returns a single interview document by ID. |
GET |
/api/interviews/[id]/feedback |
Returns the feedback document linked to a specific interview. |
POST |
/api/vapi/generate |
Accepts role, level, type, techstack, amount, and userid. Calls Gemini to generate questions and saves the interview to Firestore. Rate limit: 10 req/min. |
Agent.tsx
The core component of the application. Manages the voice interview call lifecycle using a four-state enum (INACTIVE → CONNECTING → ACTIVE → FINISHED). Registers event listeners for Vapi call start/end, live transcripts, and speech detection. When the call ends, it triggers feedback generation and redirects the user.
AuthForm.tsx
A shared form used for both sign-in and sign-up, controlled by a type prop. Uses React Hook Form with Zod validation. On sign-up, calls Firebase Client SDK then the signUp server action. On sign-in, gets an ID token from Firebase and passes it to the signIn server action to create a session cookie.
InterviewCard.tsx
An async server component that fetches the feedback document for each interview card. Displays role, interview type (color-coded badge), date, tech stack icons, score (color-coded green/amber/red), and links to feedback or interview pages.
DisplayTechIcons.tsx
Normalizes raw tech stack strings (e.g., "react.js", "Node.js") using a mappings dictionary, fetches icons from the devicons CDN, and verifies each icon exists with a HEAD request before rendering. Falls back to a generic /tech.svg if an icon is not found.
lib/actions/auth.action.ts
| Function | Description |
|---|---|
signUp(params) |
Creates a new user document in Firestore |
signIn(params) |
Verifies the Firebase ID token and issues a session cookie |
signOut() |
Deletes the session cookie |
getCurrentUser() |
Reads and verifies the session cookie, returns the user object |
isAuthenticated() |
Returns a boolean indicating whether a valid session exists |
lib/actions/general.action.ts
| Function | Description |
|---|---|
createFeedback(params) |
Sends transcript to Gemini, validates output against Zod schema, saves to Firestore |
getInterviewById(id) |
Fetches a single interview document |
getFeedbackByInterviewId(params) |
Queries feedback by interview ID and user ID |
getLatestInterviews(params) |
Returns recent finalized interviews from other users |
getInterviewsByUserId(userId) |
Returns all interviews belonging to the current user |
lib/apiResponse.ts — Typed response factory. Provides successResponse(), errorResponse(), and named helpers (errors.unauthorized(), errors.notFound(), errors.rateLimited(), etc.) for consistent API response shapes.
lib/rateLimit.ts — In-memory sliding-window rate limiter backed by a Map. Accepts a key (IP-based), max requests, and window duration. Returns { allowed, remaining, resetAt }.
lib/logger.ts — Structured JSON logger with info, warn, error, debug, and request methods. Each log entry includes level, message, timestamp, and optional context object.
lib/validation.ts — Zod schemas for paginationSchema, interviewFilterSchema, and createInterviewSchema used across API routes.
- Redis-backed rate limiting — replace the in-memory
Mapwith Upstash Redis so rate limits persist across server restarts and work correctly with multiple instances - Route-level auth middleware — add
middleware.tsto protect all authenticated routes at the edge and eliminate null assertion operators in page components - Google / GitHub OAuth — add social sign-in options alongside email/password
- Stronger password policy — enforce a minimum of 8 characters with at least one uppercase letter and one number
- Score trend charts — add analytics charts on the dashboard to visualize improvement over multiple sessions
- Integration tests — add tests for server actions and API routes using the Firebase Emulator Suite
- Batched Firestore reads — replace the per-card feedback query in the dashboard with a single batched read to reduce Firestore costs and latency
- Multi-language support — allow interviews to be conducted in languages other than English
- Video mode — optional webcam feed alongside the voice interview for more realistic practice
Dharam Dan
Full-stack developer. Built CareerVoice to help developers practice the part of interviews that reading alone cannot prepare you for — speaking clearly, thinking out loud, and staying confident under pressure.
- GitHub — github.com/dharamdan01
- LinkedIn — linkedin.com/in/dharam-dan-2584bb258
This project is licensed under the MIT License.