Skip to content

Your First App

Let's build a simple Todo API with database persistence.

Setup Database

Update docker-compose.yml (already included in generated project):

services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
      MYSQL_DATABASE: todos

Create Repository

src/Repository/TodoRepository.php:

<?php
namespace App\Repository;

use Tusk\Data\Repository\AbstractRepository;

class TodoRepository extends AbstractRepository
{
    public function findAll(): array
    {
        $stmt = $this->connection->query('SELECT * FROM todos');
        return $stmt->fetchAll(\PDO::FETCH_ASSOC);
    }

    public function create(string $title): int
    {
        $stmt = $this->connection->prepare(
            'INSERT INTO todos (title, completed) VALUES (?, 0)'
        );
        $stmt->execute([$title]);
        return (int) $this->connection->lastInsertId();
    }
}

Create Controller

src/Controller/TodoController.php:

<?php
namespace App\Controller;

use App\Repository\TodoRepository;
use Tusk\Web\Attribute\Route;
use Tusk\Web\Http\Request;
use Tusk\Web\Http\Response;

class TodoController
{
    public function __construct(
        private TodoRepository $todos
    ) {}

    #[Route('/todos', methods: ['GET'])]
    public function list(Request $request): Response
    {
        $todos = $this->todos->findAll();
        return new Response(200, ['Content-Type' => 'application/json'],
            json_encode($todos));
    }

    #[Route('/todos', methods: ['POST'])]
    public function create(Request $request): Response
    {
        $data = json_decode($request->getBody(), true);
        $id = $this->todos->create($data['title']);

        return new Response(201, ['Content-Type' => 'application/json'],
            json_encode(['id' => $id]));
    }
}

Run Migrations

Create migrations/001_create_todos.sql:

CREATE TABLE todos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    completed BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Run it:

docker-compose exec db mysql -uroot -psecret todos < migrations/001_create_todos.sql

Test It

# List todos
curl http://localhost:8080/todos

# Create todo
curl -X POST http://localhost:8080/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"Learn Tusk"}'

Congratulations! You've built a working API with Tusk. 🎉