Integrating API Requests for Dynamic Chat Interaction

Welcome back! In the previous lesson, you set up a basic chat interface using Symfony and Twig. This laid the foundation for creating a user-friendly web application. Now, we will take the next step by connecting this interface to our backend API. This connection is crucial for transforming our static interface into a dynamic, interactive chat application. By the end of this lesson, you will understand how to integrate the frontend with the backend, enabling real-time communication between the user and the server.

Connecting Twig Templates with API Endpoints

Now that we have our API endpoints set up in Symfony, we need to connect our Twig template to these endpoints. This connection allows our chat interface to communicate with the server, sending user messages and receiving responses.

Initializing Chat Variables in Twig Templates

To maintain the current user and chat state in the browser, we store both values in JavaScript variables:

{# templates/chat/chat.html.twig #}
<script>
    // Initialize variables to store the current user and chat IDs
    let currentUserId = null;
    let currentChatId = null;
</script>

These variables are populated from the API when a new chat is created. The frontend uses them when sending messages so the backend can continue the same conversation.

Understanding the Chat Template Structure

Let's examine the basic HTML structure of our chat interface:

{# templates/chat/chat.html.twig #}
<!DOCTYPE html>
<html>
<head>
    <title>Customer Service Chat</title>
</head>
<body>
    <div class="header">
        <h1>Welcome to Our Customer Service</h1>
        <p>How can we help you today?</p>
    </div>
    <div id="chat-container">
        <div id="messages"></div>
        <div class="input-container">
            <div class="input-wrapper">
                <input type="text" id="message-input" placeholder="Type your message...">
            </div>
            <button onclick="sendMessage()">Send</button>
            <button id="new-chat-btn" onclick="startNewChat()">New Chat</button>
        </div>
    </div>

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

This HTML structure creates a simple chat interface with a header, message display area, and input controls. The interface includes a text input for messages, a send button, and a button to start a new chat, providing all the necessary elements for user interaction.

The JavaScript initialization and core functions handle the chat functionality:

<script>
    // Get references to the messages container and message input field
    const messagesContainer = document.getElementById('messages');
    const messageInput = document.getElementById('message-input');

    // Initialize variables to store the current user and chat IDs
    let currentUserId = null;
    let currentChatId = null;

    // Start a chat automatically when the page loads
    document.addEventListener('DOMContentLoaded', startNewChat);
    
    function appendMessage(role, content) {
        // Create a new div element for the message
        const messageDiv = document.createElement('div');
        
        // Assign a class to the message based on its role (user or assistant)
        messageDiv.className = `message ${role}`;
        
        // Set the text content of the message
        messageDiv.textContent = content;
        
        // Append the message to the messages container
        messagesContainer.appendChild(messageDiv);
        
        // Scroll the messages container to the bottom to show the latest message
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }
</script>

This JavaScript code initializes our chat interface by getting references to key DOM elements and setting up event listeners. The appendMessage function is a utility that adds new messages to the chat display, differentiating between user and assistant messages with CSS classes, and ensures the chat always scrolls to show the most recent messages.

API Interaction Functions

The following functions interact with our Symfony backend API:

<script>
    function startNewChat() {
        fetch('/api/create_chat', {
            method: 'POST',
            credentials: 'same-origin'
        })
        .then(response => response.json())
        .then(data => {
            currentUserId = data.user_id;
            currentChatId = data.chat_id;
            messagesContainer.innerHTML = '';
        })
        .catch(() => {
            alert('Error creating chat');
        });
    }

    function sendMessage() {
        // Retrieve and trim the input value
        const message = messageInput.value.trim();
        
        // If the message is empty, do not proceed
        if (!message) return;

        // Add user message to display
        appendMessage('user', message);

        // Clear the input field after sending the message
        messageInput.value = '';

        // Send message to API
        fetch('/api/send_message', {
            method: 'POST',
            credentials: 'same-origin',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                user_id: currentUserId,
                chat_id: currentChatId,
                message: message
            })
        })
        .then(response => response.json())
        .then(data => {
            currentChatId = data.chat_id ?? currentChatId;
            appendMessage('assistant', data.message);
        })
        .catch(() => {
            alert('Error sending message');
        });
    }
</script>

The request flow in this lesson looks like this:

DOMContentLoaded
  -> POST /api/create_chat
  -> store user_id and chat_id in JavaScript
  -> user submits a message
  -> POST /api/send_message
  -> keep chat_id in sync if the response returns an updated value
  -> append assistant reply to the chat

Note: These examples use JSON POST requests tied to the current session. For this demo, we keep the frontend simple and do not add CSRF protection. In a production application, you should protect these endpoints with CSRF tokens or an equivalent mitigation strategy.

These functions handle the core API interactions for our chat application:

  • startNewChat: This function initiates a new chat session by making a POST request to our Symfony controller. When the API responds, it stores both the returned user ID and chat ID, then clears the message display so the interface is ready for a fresh conversation. We also send same-origin credentials with our requests so the session cookie is consistently included.

  • sendMessage: This function is the heart of our chat interaction. It captures the user's input, displays it in the chat, and sends a JSON request containing the current user ID, chat ID, and message. When the API responds, it displays the assistant's response and keeps the chat ID in sync if the backend returns an updated value. For user input handling, we add an event listener for the Enter key:

<script>
    // Handle Enter key
    messageInput.addEventListener('keydown', function(e) {
        if (e.key === 'Enter' && !e.shiftKey) {
            // Prevent the default form submission behavior
            e.preventDefault();
            // Send the message when Enter key is pressed
            sendMessage();
        }
    });
</script>

This event listener enhances the user experience by allowing messages to be sent with the Enter key. It prevents the default form submission behavior and calls our sendMessage function, making the chat interface more intuitive and user-friendly.

Summary and Next Steps

In this lesson, you learned how to connect the chat interface to a backend API using Symfony, Twig, and controller methods. We covered the setup of API endpoints in Symfony, the structure of a Twig template that interacts with these endpoints, and the implementation of chat functionality. This connection is a crucial step in creating a dynamic and interactive chat application.

As you move on to the practice exercises, focus on reinforcing these concepts and experimenting with the code. This hands-on practice will deepen your understanding and prepare you for the next unit, where we will continue to enhance the chatbot's capabilities.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal