Skip to content

My todo list

PHP api.

Simple server api.

php
<?php

    $host = '127.0.0.1';
    $port = '3306';
    $user = '';
    $password = '';

    try {
        $schemaName = 'MyDB';
        $pdo = new PDO("mysql:host=$host;port=$port;dbname=$schemaName", $user, $password);

        $result = [];

        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            $input = json_decode(file_get_contents('php://input'), true);
            
            $sqlQuery = '';

            $result['action'] = $input['action'];

            switch ($input['action']) {
                case "insert":
                    $sqlQuery = "INSERT INTO Todo (todo) VALUES (:todo)";                    
                    $pdoQuery = $pdo->prepare($sqlQuery);
                    $pdoQuery->execute(['todo' => $input['todo']]);

                    $result['data'] = $pdo->lastInsertId();

                    break;
                case "update":
                    $sqlQuery = "UPDATE Todo SET done= :done WHERE id= :id";
                    $pdoQuery = $pdo->prepare($sqlQuery);
                    $pdoQuery->execute(['done' => $input['done'], 'id' => $input['id']]);
                    
                    break;
                case "delete":
                    $sqlQuery = "DELETE FROM Todo WHERE id= :id";
                    $pdoQuery = $pdo->prepare($sqlQuery);
                    $pdoQuery->execute(['id' => $input['id']]);

                    break;
                case "get":
                    $sqlQuery = "SELECT * FROM Todo";
                    $res = $pdo->query($sqlQuery);
                    $result['data'] = $re
            $result['status'] = 'Ok';            

        } else {
            $result["status"] = "error";
            $result["message"] = "Method not allowed. Use POST";
        }

    } catch (Exception $e) {
        $result["status"] = "error";
        $result["message"] = $e->getMessage();
    }


    header('Content-Type: application/json; charset=utf-8');

    echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

    $pdo = null;
?>

Client access

Using fetch index.php

html
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Todo List</title>
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-SgOJa3DmI69IUzQ2PVdRZhwQ+dy64/BUtbMJw1MZ8t5HZApcHrRKUc4W0kG879m7" crossorigin="anonymous">
</head>
<body>
  <nav class="navbar navbar-dark bg-dark">
    <div class="container-fluid">
      <a class="navbar-brand">My todo list</a>
    </div>
  </nav>
    <div class="container mt-4">
        <div class="input-group mb-3">
            <input id="txt-input" type="text" class="form-control" placeholder="Todo" aria-label="Recipient's username" aria-describedby="button-addon2">
            <button class="btn btn-primary" type="button" id="button-addon">add</button>
          </div>
          
          <div id="todoList" class="list-group"></div>
        </div>
        
    </div>
    <script>
        const elTodoList = document.querySelector('#todoList')
        const elBtn = document.querySelector('#button-addon')
        const elTxt = document.querySelector('#txt-input')
        

        let html = itemTodo => `
            <div class="_item list-group-item d-flex align-items-center list-group-item-action" itemid="${itemTodo.id}">
            <img src="${ itemTodo.done != 0 ? 'check.svg' : 'minus.svg' }" class="me-2" alt="check" height="25">
            <span class="flex-grow-1">${itemTodo.todo}</span>  
            <img class="_cancel" src="cancel.svg" alt="check" height="25">                          
            </div>
        `
        let data = []
        
        elBtn.addEventListener('click', async e  => {
            let res = await TodoAPI({ action: 'insert', todo: elTxt.value })
            
            if (res.status == 'Ok') {
                let newItem = { id:res.data, todo: elTxt.value, done: 0 }
                data.push(newItem)
                elTodoList.insertAdjacentHTML('beforeend', html(newItem))
            } 
            
            console.log(res)
            elTxt.value = ''
        })

        elTodoList.addEventListener('click', async e => {            
            let elParent = e.target.parentElement
            let res

            if (elParent.classList.contains('_item')){
                let item = data.find(item => item.id == elParent.attributes.itemid.value)
                    
                if (e.target.classList.contains('_cancel')){       
                    data.splice(data.indexOf(item), 1)
                    res = await TodoAPI({ action: 'delete', id: item.id })
                } else {
                    item.done = item.done == 0 ? 1: 0
                    res = await TodoAPI({ action: 'update', done: item.done, id: item.id })
                    
                }
                console.log(res)
                
                elTodoList.innerHTML = ''

                data.forEach( item => {
                    elTodoList.insertAdjacentHTML('beforeend', html(item))
                })
            }                
        })

        async function TodoAPI(query) {
            const result = await fetch('/todo_api.php', {
                method: 'POST',
                headers: {
                    'content-Type': 'application/json'
                },
                body: JSON.stringify(query) //{ action: 'query', query: 'SELECT * FROM Todo'})
            })
            return await result.json()
        }

        async function Init() {
            const res = await TodoAPI({ action: 'get' })
            if (res.status == 'Ok') {
                data = res.data
                data.forEach( item => {
                    elTodoList.insertAdjacentHTML('beforeend', html(item))
                })
            }
            console.log(res)
        }

        Init()

    </script>

</body>
</html>
}

WebSocket

Ratchet

WebSockets for PHP

sh
sudo apt update
sudo apt install composer

Create project

sh
composer init --require=cboden/ratchet --no-interaction

Install Ratchet

sh
composer require cboden/ratchet

Server.php

php
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
use Ratchet\Http\HttpServer;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
require dirname(__DIR__) . '/html/vendor/autoload.php';

class Chat implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        $this->clients = new \SplObjectStorage;
        echo "Servidor WebSocket iniciado\n";
    }

    public function onOpen(ConnectionInterface $conn) {
        $this->clients->attach($conn);
        echo "Nova conexão: {$conn->resourceId}\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        foreach ($this->clients as $client) {
            if ($from !== $client) {
                $client->send($msg);
            }
        }
        echo "Mensagem recebida: $msg\n";
    }

    public function onClose(ConnectionInterface $conn) {
        $this->clients->detach($conn);
        echo "Conexão fechada: {$conn->resourceId}\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "Erro: {$e->getMessage()}\n";
        $conn->close();
    }
}

$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new Chat()
        )
    ),
    8080
);

$server->run();

bash

sh
php server.php

Nginx setup

sh
server {
    listen 80;
    server_name your-domain.com;

    # Configuração para PHP (FastCGI)
    location / {
        root /home/srvpi/Public/websocket-php/public; # Ajuste para o caminho correto
        index index.php index.html;
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock; # Ajuste para sua versão do PHP
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Configuração para WebSocket
    location /ws {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 86400;
    }
}