Voice-Enabled AI Chatbot with Laravel & JavaScript: Let Your App Talk Back

Jul 2, 2025

Voice-Enabled AI Chatbot with Laravel & JavaScript: Let Your App Talk Back

Discover how to build a voice-powered AI chatbot with Laravel & JavaScript. Capture speech, send it to GPT-4o, and let your app respond back audibly.

Author

Sidharth Pansari
Sidharth PansariSenior Software Engineer - I

Introduction — Let's Make Your App Talk

Have you ever thought, "What if users could just talk to my app instead of typing?"

We thought the same. Typing is fine, but speaking feels more natural — especially for quick queries, accessibility, or just building something cool.

So in this guide, we're going to build a simple voice-enabled chatbot — something that listens to what you say, sends it to OpenAI's GPT model, and then speaks the response back to you.

No React, no complex setup — just vanilla JavaScript on the frontend, and Laravel on the backend. It's clean, fast, and fun.

What's the Challenge?

The tricky part is getting all three systems to talk to each other:

  • The browser needs to hear your voice and turn it into text (using the Web Speech API).
  • The backend needs to process that text and generate a response (via OpenAI).
  • The browser needs to speak the response out loud again (using SpeechSynthesis).

You'll also have to deal with:

  • Browser compatibility
  • Microphone permissions
  • Network delays
  • And of course, OpenAI rate limits 

But don't worry — we'll walk through every step. Think of this like a casual pair-programming session where you're sitting next to me, and we're building this together.

Step 1: Setting Up the Laravel Backend

Let's get the backend ready to receive voice input and send it to OpenAI.

Install Required Package

We'll use Laravel's HTTP client to talk to OpenAI. To make it smoother, we'll use the official OpenAI PHP SDK:

composer require openai-php/laravel

Then, in your .env file, add your OpenAI API key:

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx


That's it — we're ready to hit OpenAI's API.

The Controller

We created a controller called VoiceChatbotController. It has two methods:

index() — loads the main page

handle() — receives the transcript and sends it to OpenAI

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;

class VoiceChatbotController extends Controller
{
    public function index()
    {
        return view('voice-chatbot');
    }

    public function handle(Request $request)
    {
        $text = $request->input('text');
        $apiKey = env('OPENAI_API_KEY');

        if (!$apiKey) {
            return response()->json(['error' => 'OpenAI API key not configured'], 500);
        }

        try {
            $response = Http::withHeaders([
                'Authorization' => 'Bearer ' . $apiKey,
            ])->post('https://api.openai.com/v1/chat/completions', [
                'model' => 'gpt-4o',
                'messages' => [
                    ['role' => 'user', 'content' => $text],
                ],
            ]);

            if ($response->successful()) {
                $reply = $response->json('choices.0.message.content');
                return response()->json(['reply' => $reply]);
            } else {
                return response()->json(['error' => 'Failed to get response from OpenAI'], 500);
            }
        } catch (\Exception $e) {
            return response()->json(['error' => 'Internal server error'], 500);
        }
    }
}

Routes

Set up the routes to serve the chatbot view and handle the API request.

In your web.php:

use App\Http\Controllers\VoiceChatbotController;
Route::get('/', [VoiceChatbotController::class, 'index']);
Route::get('/voice-chatbot', [VoiceChatbotController::class, 'index'])->name('voice-chatbot');

In your api.php:

use App\Http\Controllers\VoiceChatbotController;
Route::post('/chat', [VoiceChatbotController::class, 'handle'])->name('api.chat');

That's it for Step 1!

Your backend is now:

  • Ready to receive spoken input as plain text
  • Talking to OpenAI using GPT-4o
  • Returning an AI-generated reply as JSON

Next up, we'll work on capturing the user's voice in the browser and sending it to this endpoint.

Step 2: Capturing Voice in the Browser (Using Web Speech API)

Alright — let's build the complete frontend interface. This includes the HTML structure, speech recognition setup, and all the user experience details that make it feel professional.

Now, if you're thinking: "Do I need to install anything here?" — Nope! Modern browsers (especially Chrome and Edge) already support this via the Web Speech API.

HTML Structure

First, let's create the complete HTML structure in your voice-chatbot.blade.php:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 50px auto;
            padding: 20px;
            text-align: center;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            color: white;
        }
        .container {
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            border-radius: 20px;
            padding: 40px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
        }
        button {
            padding: 15px 30px;
            margin: 10px;
            font-size: 16px;
            cursor: pointer;
            border: none;
            border-radius: 50px;
            transition: all 0.3s ease;
            font-weight: bold;
        }
        #startBtn {
            background: #4CAF50;
            color: white;
        }
        #stopBtn {
            background: #f44336;
            color: white;
        }
        button:disabled {
            opacity: 0.6;
            cursor: not-allowed;
        }
        #status {
            margin: 20px 0;
            font-size: 18px;
        }
        #output {
            margin: 30px 0;
            padding: 20px;
            border: 2px solid rgba(255, 255, 255, 0.2);
            border-radius: 15px;
            min-height: 100px;
            background: rgba(255, 255, 255, 0.05);
            text-align: left;
            white-space: pre-wrap;
            font-size: 16px;
            line-height: 1.6;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🎤 Voice Chatbot</h1>
        
        <div>
            <button id="startBtn" onclick="startListening()">▶️ Start Listening</button>
            <button id="stopBtn" onclick="stopListening()" disabled>⏹ Stop Listening</button>
        </div>

        <div id="status">Click Start to begin...</div>
        <div id="output"></div>
    </div>

    <script>
        // JavaScript will go here...
    </script>
</body>
</html>

This gives us a beautiful glassmorphism design with proper button states and feedback areas.

Setting Up Speech Recognition

Now let's add the core JavaScript inside the <script> tag:

const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
recognition.lang = 'en-US';
recognition.interimResults = false;
recognition.continuous = false;

let transcript = '';

Let's break that down:

  • We create a recognition object using the browser's built-in speech recognition.
  • lang = 'en-US' sets the language to English (you can change this later).
  • interimResults = false means we only care about the final result.
  • continuous = false means it stops listening after a single sentence or phrase.

Essential Utility Functions

These functions handle UI updates and button states:

function updateStatus(message) {
    document.getElementById('status').innerText = message;
}

function updateOutput(message) {
    document.getElementById('output').innerText = message;
}

function resetButtons() {
    document.getElementById('startBtn').disabled = false;
    document.getElementById('stopBtn').disabled = true;
}

Button Control Functions

Here are the functions that control recording:

function startListening() {
    transcript = '';
    updateOutput('');
    recognition.start();
}

function stopListening() {
    recognition.stop();
}

Speech Recognition Event Handlers

Now for the actual speech recognition magic:

recognition.onstart = function() {
    document.getElementById('startBtn').disabled = true;
    document.getElementById('stopBtn').disabled = false;
    updateStatus('🎤 Listening... Speak now!');
};

recognition.onresult = function(event) {
    transcript = event.results[0][0].transcript;
    updateOutput(`You said: ${transcript}`);
    updateStatus('Processing...');
};

recognition.onerror = function(event) {
    updateStatus(`Error: ${event.error}`);
    resetButtons();
};

recognition.onend = function() {
    if (!transcript) {
        updateStatus('No speech detected. Click Start to try again.');
        resetButtons();
        return;
    }
    
    updateStatus('Speech captured! Ready to send to AI...');
    resetButtons();
};

This handles:

  1. Button state management - Proper disable/enable during recording
  2. Visual feedback - Clear status messages throughout the process
  3. Speech capture - Stores what the user said
  4. Error handling - Graceful handling of speech recognition issues

At This Point…

You now have a complete, professional-looking interface that can: Start and stop voice capture with proper button states, Transcribe what you say with visual feedback, Store the transcript for processing.
Display clear status messages and results Look beautiful with modern glassmorphism design

Step 3: Complete Voice-to-AI-to-Speech Flow

Now it's time to connect everything together. We'll modify the recognition.onend function to:

  1. Send the transcript to Laravel
  2. Get the AI response
  3. Speak it back to the user

The Complete Implementation

Replace the simple recognition.onend from Step 2 with this complete version:

recognition.onend = async function () {
    if (!transcript) {
        updateStatus('No speech detected. Click Start to try again.');
        resetButtons();
        return;
    }

    try {
        updateStatus('Sending to AI...');

        const response = await fetch('{{ route("api.chat") }}', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
            },
            body: JSON.stringify({ text: transcript })
        });

        const data = await response.json();

        if (data.error) {
            updateOutput(`Error: ${data.error}`);
            updateStatus('Error occurred. Try again.');
        } else {
            updateOutput(`You said: ${transcript}\n\nAI replied: ${data.reply}`);
            
            const utter = new SpeechSynthesisUtterance(data.reply);
            speechSynthesis.speak(utter);
            
            updateStatus('Response received! Click Start for another conversation.');
        }
    } catch (error) {
        updateOutput(`Error: ${error.message}`);
        updateStatus('Something went wrong. Try again.');
    }

    resetButtons();
};

Security Note

Don't forget to include the CSRF token meta tag in your blade template:

<meta name="csrf-token" content="{{ csrf_token() }}">

Let's Break Down What Happens

  1. Capture: User speaks, speech is transcribed
  2. Send: Transcript is sent to Laravel via POST request
  3. Process: Laravel sends text to OpenAI and gets AI response
  4. Receive: JavaScript gets the AI reply as JSON
  5. Speak: Browser uses SpeechSynthesis to read the reply aloud
  6. Reset: UI resets for next conversation

What You've Built

Congratulations! You now have a complete voice interaction system:

  • User clicks Start and speaks
  • Transcript captured and sent to Laravel
  • Laravel sends to OpenAI and gets a reply
  • Browser speaks the AI's answer back

That's a full end-to-end voice interaction — from mic to model to mouth.

Conclusion – Let Your App Talk Back

And there you have it — a fully working voice-enabled AI chatbot built with just Laravel, JavaScript, and the OpenAI API.

You:

  • Captured the user's voice
  • Transcribed it using the Web Speech API
  • Sent it to Laravel for processing
  • Passed it to GPT-4o via OpenAI
  • Got a smart reply, and
  • Spoke the response aloud using SpeechSynthesis

No third-party libraries. No frontend frameworks. Just pure browser APIs and Laravel handling the backend logic.

This isn't just a cool demo — it opens up real use cases:

  • Customer support bots
  • Interactive tutorials
  • Voice interfaces for accessibility
  • Internal tools where users prefer voice over typing

Bonus Ideas You Can Try

If you want to take this even further, here are some ways to level up:

1. Add Roles or Personalities

Let the AI behave like a tutor, assistant, or support agent using system messages in OpenAI's API.

'messages' => [
    ['role' => 'system', 'content' => 'You are a helpful medical assistant.'],
    ['role' => 'user', 'content' => $text],
],

2. Support Multiple Languages

Use recognition.lang = 'hi-IN' or 'es-ES' for Hindi, Spanish, etc. You can also translate results using OpenAI or Google Translate APIs before reading them aloud.

3. Add Memory or Context

Right now, your bot responds statelessly. But you could maintain a history of messages and pass them all in the API call for a more conversational experience.

 4. Secure It for Production

  • Limit usage with rate-limiting middleware
  • Cache responses
  • Avoid exposing sensitive API tokens in the frontend

That's a Wrap!

This tutorial showed you how to blend speech, AI, and Laravel into a conversational interface with a surprisingly simple setup.

We hope you learned something useful — and more importantly, had fun building it.

Subscribe to Our Newsletter

More from the engineering frontline.

Dive deep into our research and insights on design, development, and the impact of various trends to businesses.
Insight
Building Local LLMs Using Dart FFI And llama.cpp: Beyond Wrapper Packages
Sep 11, 2026

Building Local LLMs Using Dart FFI And llama.cpp: Beyond Wrapper Packages

Build local LLMs in Flutter with Dart FFI and llama.cpp, and see how native bridges, GGUF models, memory management, and token streaming enable private, on-device AI.

Insight
My Flutter App Froze With Three Photos on Screen. Here's What I Was Doing Wrong
Sep 11, 2026

My Flutter App Froze With Three Photos on Screen. Here's What I Was Doing Wrong

This blog explains how rethinking Flutter’s image-processing architecture fixed severe performance issues and improved rendering efficiency.

Insight
Building a Production-Ready Canva-like Editor with Konva.js, React 19 and Next.js 15
Sep 10, 2026

Building a Production-Ready Canva-like Editor with Konva.js, React 19 and Next.js 15

This blog explains how to build a production-ready canvas editor with Konva.js, React, and Next.js, covering architecture, performance, and key engineering decisions.

Insight
What a PHP-to-NestJS Banking Migration Taught Us About Architecture, Security, and Trust
Sep 8, 2026

What a PHP-to-NestJS Banking Migration Taught Us About Architecture, Security, and Trust

This blog explores the architecture, security, performance, and documentation lessons from migrating a legacy PHP/Laravel banking platform to NestJS.

Insight
Building Production-Grade Video Thumbnail Scrubbing in the Browser: HLS, Frame Extraction, Caching, and Performance Trade-offs
Sep 7, 2026

Building Production-Grade Video Thumbnail Scrubbing in the Browser: HLS, Frame Extraction, Caching, and Performance Trade-offs

This blog explains how to build responsive video thumbnail scrubbing in the browser for local files and HLS streams, covering frame extraction, caching, and performance trade-offs.

Insight
The Agent Can See Your App. How Often Can It Look?
Sep 4, 2026

The Agent Can See Your App. How Often Can It Look?

AI coding agents can now interact with mobile apps, but their effectiveness depends on iteration speed. This blog explores how React Native architecture influences feedback loops and AI-driven developer productivity.

Insight
Building Interactive Cards from Design JSON Without Killing Your Feed: Overlays, Video, Mute/Unmute, and Lag-Free Lists
Sep 1, 2026

Building Interactive Cards from Design JSON Without Killing Your Feed: Overlays, Video, Mute/Unmute, and Lag-Free Lists

Learn how to turn design JSON into interactive, video-enabled cards using overlays, smart media controls, caching, and virtualization without slowing down high-cardinality feeds.

The Right Conversation Can

Save You Six Months.

Book a call
Voice-Enabled AI Chatbot with Laravel & JavaScript: Let Your App Talk Back. - GeekyAnts