In today’s digital age, real-time communication is crucial for enhancing user engagement and experience. One of the most popular ways to achieve real-time messaging is by using WebSockets, a protocol that enables bi-directional communication between a server and clients. In this tutorial, we’ll explore how to build a real-time chat application using CakePHP, a powerful PHP framework, combined with the magic of WebSockets. Let’s dive in!

1. Prerequisites
Before we start, make sure you have the following prerequisites installed on your system:
- PHP 7.2 or higher
- Composer
- CakePHP 4.x or higher
- A modern web browser that supports WebSockets
2. Setting up the Project
First, let’s create a new CakePHP project. Open your terminal and run the following command:
composer create-project --prefer-dist cakephp/app realtime-chat-app
bash
composer create-project --prefer-dist cakephp/app realtime-chat-app
cd realtime-chat-app
bash
composer create-project --prefer-dist cakephp/app realtime-chat-app
cd realtime-chat-app
3. Setting up the Database
For our real-time chat application, we need to set up a database to store messages. In this example, we’ll use a simple “messages” table. Run the following CakePHP migration command to create the table:
bin/cake bake migration CreateMessages
bash
bin/cake bake migration CreateMessages
bash
bin/cake bake migration CreateMessages
In the generated migration file, modify the up() method to create the “messages” table:
// config/Migrations/20230717000000_CreateMessages.php
use Cake\Database\Driver\Mysql;
use Migrations\AbstractMigration;
class CreateMessages extends AbstractMigration
$table = $this->table('messages');
$table->addColumn('user_id', 'integer', ['null' => false])
->addColumn('content', 'text', ['null' => false])
->addColumn('created', 'datetime', ['default' => null])
php
// config/Migrations/20230717000000_CreateMessages.php
use Cake\Database\Driver\Mysql;
use Migrations\AbstractMigration;
class CreateMessages extends AbstractMigration
{
public function change()
{
$table = $this->table('messages');
$table->addColumn('user_id', 'integer', ['null' => false])
->addColumn('content', 'text', ['null' => false])
->addColumn('created', 'datetime', ['default' => null])
->addIndex(['user_id'])
->create();
}
}
php
// config/Migrations/20230717000000_CreateMessages.php
use Cake\Database\Driver\Mysql;
use Migrations\AbstractMigration;
class CreateMessages extends AbstractMigration
{
public function change()
{
$table = $this->table('messages');
$table->addColumn('user_id', 'integer', ['null' => false])
->addColumn('content', 'text', ['null' => false])
->addColumn('created', 'datetime', ['default' => null])
->addIndex(['user_id'])
->create();
}
}
Now, apply the migration to create the “messages” table:
bin/cake migrations migrate
bash
bin/cake migrations migrate
bash
bin/cake migrations migrate
4. Creating the Chat Interface
Next, let’s set up the user interface for our real-time chat application. We’ll use HTML, CSS, and JavaScript for this purpose.
1. Create a new template file for the chat interface:
touch templates/Chat/index.ctp
bash
mkdir -p templates/Chat
touch templates/Chat/index.ctp
bash
mkdir -p templates/Chat
touch templates/Chat/index.ctp
2. Add the following HTML structure to index.ctp:
<!-- templates/Chat/index.ctp -->
<title>Real-Time Chat Application</title>
<!-- Add your CSS styles here -->
<div id="chat-messages"></div>
<input type="text" id="message-input" placeholder="Type your message here...">
<button id="send-button">Send</button>
<!-- Add your JavaScript code here -->
html
<!-- templates/Chat/index.ctp -->
<!DOCTYPE html>
<html>
<head>
<title>Real-Time Chat Application</title>
<!-- Add your CSS styles here -->
</head>
<body>
<div id="chat-box">
<div id="chat-messages"></div>
<div id="user-input">
<input type="text" id="message-input" placeholder="Type your message here...">
<button id="send-button">Send</button>
</div>
</div>
<!-- Add your JavaScript code here -->
</body>
</html>
html
<!-- templates/Chat/index.ctp -->
<!DOCTYPE html>
<html>
<head>
<title>Real-Time Chat Application</title>
<!-- Add your CSS styles here -->
</head>
<body>
<div id="chat-box">
<div id="chat-messages"></div>
<div id="user-input">
<input type="text" id="message-input" placeholder="Type your message here...">
<button id="send-button">Send</button>
</div>
</div>
<!-- Add your JavaScript code here -->
</body>
</html>
3. Now, let’s implement the necessary JavaScript code to handle WebSocket connections and message sending:
<!-- templates/Chat/index.ctp -->
<title>Real-Time Chat Application</title>
<!-- Add your CSS styles here -->
<div id="chat-messages"></div>
<input type="text" id="message-input" placeholder="Type your message here...">
<button id="send-button">Send</button>
const socket = new WebSocket('ws://localhost:8080');
// Handle WebSocket connection
console.log('WebSocket connected');
// Handle incoming messages
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
appendMessage(message.user_id, message.content, 'received');
// Send messages to the server
document.getElementById('send-button').addEventListener('click', () => {
const messageInput = document.getElementById('message-input');
const messageContent = messageInput.value.trim();
if (messageContent !== '') {
user_id: 1, // Replace with the actual user ID
socket.send(JSON.stringify(message));
appendMessage(message.user_id, messageContent, 'sent');
// Function to display messages on the chat interface
function appendMessage(userId, content, messageType) {
const chatMessages = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = messageType;
messageDiv.innerText = `User ${userId}: ${content}`;
chatMessages.appendChild(messageDiv);
html
<!-- templates/Chat/index.ctp -->
<!DOCTYPE html>
<html>
<head>
<title>Real-Time Chat Application</title>
<!-- Add your CSS styles here -->
</head>
<body>
<div id="chat-box">
<div id="chat-messages"></div>
<div id="user-input">
<input type="text" id="message-input" placeholder="Type your message here...">
<button id="send-button">Send</button>
</div>
</div>
<script>
const socket = new WebSocket('ws://localhost:8080');
// Handle WebSocket connection
socket.onopen = () => {
console.log('WebSocket connected');
};
// Handle incoming messages
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
appendMessage(message.user_id, message.content, 'received');
};
// Send messages to the server
document.getElementById('send-button').addEventListener('click', () => {
const messageInput = document.getElementById('message-input');
const messageContent = messageInput.value.trim();
if (messageContent !== '') {
const message = {
user_id: 1, // Replace with the actual user ID
content: messageContent,
};
socket.send(JSON.stringify(message));
appendMessage(message.user_id, messageContent, 'sent');
messageInput.value = '';
}
});
// Function to display messages on the chat interface
function appendMessage(userId, content, messageType) {
const chatMessages = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = messageType;
messageDiv.innerText = `User ${userId}: ${content}`;
chatMessages.appendChild(messageDiv);
}
</script>
</body>
</html>
html
<!-- templates/Chat/index.ctp -->
<!DOCTYPE html>
<html>
<head>
<title>Real-Time Chat Application</title>
<!-- Add your CSS styles here -->
</head>
<body>
<div id="chat-box">
<div id="chat-messages"></div>
<div id="user-input">
<input type="text" id="message-input" placeholder="Type your message here...">
<button id="send-button">Send</button>
</div>
</div>
<script>
const socket = new WebSocket('ws://localhost:8080');
// Handle WebSocket connection
socket.onopen = () => {
console.log('WebSocket connected');
};
// Handle incoming messages
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
appendMessage(message.user_id, message.content, 'received');
};
// Send messages to the server
document.getElementById('send-button').addEventListener('click', () => {
const messageInput = document.getElementById('message-input');
const messageContent = messageInput.value.trim();
if (messageContent !== '') {
const message = {
user_id: 1, // Replace with the actual user ID
content: messageContent,
};
socket.send(JSON.stringify(message));
appendMessage(message.user_id, messageContent, 'sent');
messageInput.value = '';
}
});
// Function to display messages on the chat interface
function appendMessage(userId, content, messageType) {
const chatMessages = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = messageType;
messageDiv.innerText = `User ${userId}: ${content}`;
chatMessages.appendChild(messageDiv);
}
</script>
</body>
</html>
4. Creating the WebSocket Server
Now that we have the chat interface set up, let’s create the WebSocket server using Ratchet, a PHP library that provides WebSocket support.
1. Install Ratchet via Composer:
composer require cboden/ratchet
bash
composer require cboden/ratchet
bash
composer require cboden/ratchet
2. Create a new PHP file for the WebSocket server:
touch src/Chat/ChatServer.php
bash
mkdir src/Chat
touch src/Chat/ChatServer.php
bash
mkdir src/Chat
touch src/Chat/ChatServer.php
3. Implement the WebSocket server in ChatServer.php:
// src/Chat/ChatServer.php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ChatServer implements MessageComponentInterface
public function __construct()
$this->clients = new \SplObjectStorage;
public function onOpen(ConnectionInterface $conn)
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
public function onMessage(ConnectionInterface $from, $msg)
$data = json_decode($msg, true);
// You can handle the received message here, such as saving it to the database
foreach ($this->clients as $client) {
public function onClose(ConnectionInterface $conn)
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
public function onError(ConnectionInterface $conn, \Exception $e)
echo "An error occurred: {$e->getMessage()}\n";
php
// src/Chat/ChatServer.php
namespace App\Chat;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ChatServer implements MessageComponentInterface
{
protected $clients;
public function __construct()
{
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg)
{
$data = json_decode($msg, true);
// You can handle the received message here, such as saving it to the database
foreach ($this->clients as $client) {
if ($from !== $client) {
$client->send($msg);
}
}
}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "An error occurred: {$e->getMessage()}\n";
$conn->close();
}
}
php
// src/Chat/ChatServer.php
namespace App\Chat;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ChatServer implements MessageComponentInterface
{
protected $clients;
public function __construct()
{
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
echo "New connection! ({$conn->resourceId})\n";
}
public function onMessage(ConnectionInterface $from, $msg)
{
$data = json_decode($msg, true);
// You can handle the received message here, such as saving it to the database
foreach ($this->clients as $client) {
if ($from !== $client) {
$client->send($msg);
}
}
}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
echo "Connection {$conn->resourceId} has disconnected\n";
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "An error occurred: {$e->getMessage()}\n";
$conn->close();
}
}
4. To run the WebSocket server, create a new PHP file websocket-server.php:
touch websocket-server.php
bash
touch websocket-server.php
bash
touch websocket-server.php
5. Implement the server code in websocket-server.php:
require dirname(__DIR__) . '/vendor/autoload.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
$server = IoServer::factory(
echo "WebSocket server started\n";
php
// websocket-server.php
require dirname(__DIR__) . '/vendor/autoload.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use App\Chat\ChatServer;
$server = IoServer::factory(
new HttpServer(
new WsServer(
new ChatServer()
)
),
8080
);
echo "WebSocket server started\n";
$server->run();
php
// websocket-server.php
require dirname(__DIR__) . '/vendor/autoload.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use App\Chat\ChatServer;
$server = IoServer::factory(
new HttpServer(
new WsServer(
new ChatServer()
)
),
8080
);
echo "WebSocket server started\n";
$server->run();
5. Connecting the WebSocket Server to CakePHP
To connect the WebSocket server to our CakePHP application, we’ll use a shell process to run the WebSocket server alongside the CakePHP application.
1. Create a new shell command to start the WebSocket server:
touch src/Shell/ChatShell.php
bash
mkdir src/Shell
touch src/Shell/ChatShell.php
bash
mkdir src/Shell
touch src/Shell/ChatShell.php
2. Implement the shell command in ChatShell.php:
// src/Shell/ChatShell.php
class ChatShell extends Shell
$this->out($this->OptionParser->help());
$this->out('Starting WebSocket server...');
exec('php websocket-server.php');
php
// src/Shell/ChatShell.php
namespace App\Shell;
use Cake\Console\Shell;
class ChatShell extends Shell
{
public function main()
{
$this->out($this->OptionParser->help());
}
public function start()
{
$this->out('Starting WebSocket server...');
exec('php websocket-server.php');
}
}
php
// src/Shell/ChatShell.php
namespace App\Shell;
use Cake\Console\Shell;
class ChatShell extends Shell
{
public function main()
{
$this->out($this->OptionParser->help());
}
public function start()
{
$this->out('Starting WebSocket server...');
exec('php websocket-server.php');
}
}
3. Now, we need to create a new route to access the chat interface and start the WebSocket server:
Open config/routes.php and add the following route:
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
$routes->scope('/', function (RouteBuilder $builder) {
$builder->connect('/chat', ['controller' => 'Chat', 'action' => 'index']);
$routes->fallbacks(DashedRoute::class);
php
// config/routes.php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
$routes->scope('/', function (RouteBuilder $builder) {
// Other routes...
// Add the chat route
$builder->connect('/chat', ['controller' => 'Chat', 'action' => 'index']);
});
$routes->fallbacks(DashedRoute::class);
php
// config/routes.php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\RouteBuilder;
$routes->scope('/', function (RouteBuilder $builder) {
// Other routes...
// Add the chat route
$builder->connect('/chat', ['controller' => 'Chat', 'action' => 'index']);
});
$routes->fallbacks(DashedRoute::class);
6. Final Touches
With everything in place, you can now run your CakePHP application and WebSocket server.
1. Run the WebSocket server in a separate terminal window:
bash
bin/cake chat start
2. Start your CakePHP application server:
bash
bin/cake server
Visit http://localhost:8765/chat in your web browser to access the real-time chat interface.
Conclusion
In this tutorial, we explored how to build a real-time chat application using CakePHP and WebSockets. By combining the power of CakePHP’s backend capabilities with the real-time communication provided by WebSockets, you can create a feature-rich and engaging chat experience for your users. Now that you have the foundation, feel free to enhance the application further by adding features like user authentication, message history, and more. Happy coding!