Início Rápido — Módulo em 5 minutos
Crie um módulo de Tarefas (To-Do) completo e funcional do zero.
Um módulo Tarefa com CRUD completo: listar, criar, buscar, atualizar e deletar tarefas. Com banco de dados, autenticação e rotas funcionando.
Você pode usar closures diretamente nas rotas, sem criar nenhum Controller. O sistema aceita qualquer callable como handler. Veja o Exemplo Completo para o padrão com todas as camadas.
$router->get('/api/ping', function (Request $request): Response {
return Response::json(['pong' => true]);
});
Criar o projeto na IDE
- Acesse
/dashboard/ideno menu principal - Clique em "Novo Projeto"
- Preencha os campos:
- Nome do projeto:
Módulo de Tarefas - Nome do módulo:
Tarefa(PascalCase, sem espaços)
- Nome do projeto:
- Marque "Gerar estrutura padrão automaticamente"
- Clique em "Criar Projeto"
A IDE cria 16 arquivos organizados em pastas: Controllers, Models, Repositories, Routes, Database (Migrations e Seeders), Views e Config. Você só precisa editar 4 arquivos para ter um CRUD completo funcionando.
Editar a Migration (criar a tabela)
Abra o arquivo de migration em Database/Migrations/ (algo como YYYY_MM_DD_HHMMSS_create_tarefas_table.php) no editor e substitua o conteúdo por:
<?php
use PDO;
return [
'up' => function (PDO $pdo): void {
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
if ($driver === 'pgsql') {
$pdo->exec("CREATE TABLE IF NOT EXISTS tarefas (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
titulo VARCHAR(255) NOT NULL,
concluida BOOLEAN NOT NULL DEFAULT FALSE,
user_id VARCHAR(255) NOT NULL,
criado_em TIMESTAMPTZ NOT NULL DEFAULT NOW()
)");
} else {
$pdo->exec("CREATE TABLE IF NOT EXISTS tarefas (
id CHAR(36) PRIMARY KEY,
titulo VARCHAR(255) NOT NULL,
concluida TINYINT(1) NOT NULL DEFAULT 0,
user_id VARCHAR(255) NOT NULL,
criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
}
},
'down' => function (PDO $pdo): void {
$pdo->exec("DROP TABLE IF EXISTS tarefas");
},
];
Editar o Repository (queries SQL)
Abra Repositories/TarefaRepository.php no editor e substitua por:
<?php
namespace Src\Modules\Tarefa\Repositories;
use PDO;
final class TarefaRepository
{
private string $table = 'tarefas';
public function __construct(private readonly PDO $pdo) {}
public function findByUser(string $userId): array
{
$stmt = $this->pdo->prepare(
"SELECT * FROM {$this->table} WHERE user_id = ? ORDER BY criado_em DESC"
);
$stmt->execute([$userId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function findById(string $id, string $userId): ?array
{
$stmt = $this->pdo->prepare(
"SELECT * FROM {$this->table} WHERE id = ? AND user_id = ?"
);
$stmt->execute([$id, $userId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row !== false ? $row : null;
}
public function create(array $data): array
{
$driver = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$id = $driver === 'pgsql'
? $this->pdo->query('SELECT gen_random_uuid()')->fetchColumn()
: bin2hex(random_bytes(16));
$this->pdo->prepare(
"INSERT INTO {$this->table} (id, titulo, user_id) VALUES (?, ?, ?)"
)->execute([$id, $data['titulo'], $data['user_id']]);
return $this->findById($id, $data['user_id']) ?? ['id' => $id];
}
public function update(string $id, string $userId, array $data): void
{
$this->pdo->prepare(
"UPDATE {$this->table} SET titulo = ?, concluida = ? WHERE id = ? AND user_id = ?"
)->execute([$data['titulo'], $data['concluida'] ? 1 : 0, $id, $userId]);
}
public function delete(string $id, string $userId): void
{
$this->pdo->prepare(
"DELETE FROM {$this->table} WHERE id = ? AND user_id = ?"
)->execute([$id, $userId]);
}
}
Editar o Controller (endpoints)
Abra Controllers/TarefaController.php no editor e substitua por:
<?php
namespace Src\Modules\Tarefa\Controllers;
use Src\Kernel\Http\Request\Request;
use Src\Kernel\Http\Response\Response;
use Src\Modules\Tarefa\Repositories\TarefaRepository;
final class TarefaController
{
public function __construct(
private readonly TarefaRepository $repository
) {}
public function listar(Request $request): Response
{
$userId = $request->attribute('auth_user')->getUuid()->toString();
return Response::json(['tarefas' => $this->repository->findByUser($userId)]);
}
public function criar(Request $request): Response
{
$userId = $request->attribute('auth_user')->getUuid()->toString();
$titulo = trim($request->body['titulo'] ?? '');
if ($titulo === '') {
return Response::json(['error' => 'O campo titulo e obrigatorio.'], 422);
}
$tarefa = $this->repository->create(['titulo' => $titulo, 'user_id' => $userId]);
return Response::json(['tarefa' => $tarefa], 201);
}
public function buscar(Request $request): Response
{
$userId = $request->attribute('auth_user')->getUuid()->toString();
$tarefa = $this->repository->findById($request->params['id'], $userId);
if ($tarefa === null) {
return Response::json(['error' => 'Tarefa nao encontrada.'], 404);
}
return Response::json(['tarefa' => $tarefa]);
}
public function atualizar(Request $request): Response
{
$userId = $request->attribute('auth_user')->getUuid()->toString();
$id = $request->params['id'];
if ($this->repository->findById($id, $userId) === null) {
return Response::json(['error' => 'Tarefa nao encontrada.'], 404);
}
$this->repository->update($id, $userId, [
'titulo' => trim($request->body['titulo'] ?? ''),
'concluida' => (bool) ($request->body['concluida'] ?? false),
]);
return Response::json(['updated' => true]);
}
public function deletar(Request $request): Response
{
$userId = $request->attribute('auth_user')->getUuid()->toString();
$id = $request->params['id'];
if ($this->repository->findById($id, $userId) === null) {
return Response::json(['error' => 'Tarefa nao encontrada.'], 404);
}
$this->repository->delete($id, $userId);
return Response::json(['deleted' => true]);
}
}
Editar as Rotas
Abra Routes/web.php no editor e substitua por:
<?php
use Src\Modules\Tarefa\Controllers\TarefaController;
use Src\Kernel\Middlewares\AuthHybridMiddleware;
/** @var \Src\Kernel\Contracts\RouterInterface $router */
$auth = [AuthHybridMiddleware::class];
$router->get('/api/tarefa', [TarefaController::class, 'listar'], $auth);
$router->post('/api/tarefa', [TarefaController::class, 'criar'], $auth);
$router->get('/api/tarefa/{id}', [TarefaController::class, 'buscar'], $auth);
$router->put('/api/tarefa/{id}', [TarefaController::class, 'atualizar'], $auth);
$router->delete('/api/tarefa/{id}', [TarefaController::class, 'deletar'], $auth);
Ativar o módulo
- No painel lateral da IDE, clique em "Analisar código"
- Aguarde a análise — deve aparecer "✓ Módulo aprovado"
- Clique em "Ativar"
O sistema sincroniza os arquivos do projeto (que estão no banco de dados da IDE) para o disco em src/Modules/Tarefa/, registra o módulo como ativo no sistema e recarrega o PHP-FPM para que as rotas sejam reconhecidas imediatamente.
Executar as Migrations
Após ativar o módulo, você precisa executar as migrations manualmente para criar a tabela no banco de dados.
- Abra o Terminal da IDE (pressione
Ctrl + `ou clique no ícone do terminal) - Execute o comando de migration:
php vupi migrate --modules - Você deve ver a mensagem indicando que a migration foi executada com sucesso
Você pode verificar se a tabela tarefas foi criada acessando o Gerenciador de Banco de Dados na IDE ou consultando diretamente o banco de dados.
Testar as rotas
Pressione Ctrl + T (ou Cmd + T no Mac) para abrir o API Route Tester integrado.
O API Route Tester não envia cookies automaticamente. Para testar rotas protegidas com AuthHybridMiddleware, você precisa obter um token JWT e configurá-lo na aba Auth → Bearer Token. Sem isso, a rota retorna 401 Unauthorized.
Passo 1 — Obter o token JWT
Primeiro, faça login para obter um token de acesso:
Método: POST
URL: /api/auth/login
Aba: Body → JSON
Body:
{
"login": "seu_email_ou_username",
"senha": "sua_senha"
}
Resposta esperada:
{
"status": "success",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...", ← copie este valor
"expires_in": 3600,
"usuario": {
"uuid": "...",
"nome_completo": "Seu Nome",
"username": "seu_username",
"email": "[email protected]",
"nivel_acesso": "admin_system"
}
}
Você precisa criar um usuário primeiro. Acesse o sistema e crie uma conta através da interface web, ou peça ao administrador para criar um usuário para você.
Passo 2 — Configurar autenticação no Tester
- No API Route Tester, clique na aba "Auth"
- Selecione "Bearer Token"
- Cole o valor do
access_tokenobtido no passo anterior
Passo 3 — Criar uma tarefa
Método: POST
URL: /api/tarefa
Aba: Auth → Bearer Token (já configurado)
Aba: Body → JSON
Body:
{
"titulo": "Minha primeira tarefa"
}
Resposta esperada:
{
"tarefa": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"titulo": "Minha primeira tarefa",
"concluida": "0",
"user_id": "...",
"criado_em": "2026-05-17 10:30:00"
}
}
Passo 4 — Listar todas as tarefas
Método: GET
URL: /api/tarefa
Aba: Auth → Bearer Token (já configurado)
Resposta esperada:
{
"tarefas": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"titulo": "Minha primeira tarefa",
"concluida": "0",
"user_id": "...",
"criado_em": "2026-05-17 10:30:00"
}
]
}
Passo 5 — Buscar uma tarefa específica
Método: GET
URL: /api/tarefa/{id}
(substitua {id} pelo ID real da tarefa)
Aba: Auth → Bearer Token (já configurado)
Resposta esperada:
{
"tarefa": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"titulo": "Minha primeira tarefa",
"concluida": "0",
"user_id": "...",
"criado_em": "2026-05-17 10:30:00"
}
}
Passo 6 — Atualizar uma tarefa
Método: PUT
URL: /api/tarefa/{id}
Aba: Auth → Bearer Token (já configurado)
Aba: Body → JSON
Body:
{
"titulo": "Minha primeira tarefa (atualizada)",
"concluida": true
}
Resposta esperada:
{
"updated": true
}
Passo 7 — Deletar uma tarefa
Método: DELETE
URL: /api/tarefa/{id}
Aba: Auth → Bearer Token (já configurado)
Resposta esperada:
{
"deleted": true
}
Você criou um módulo de tarefas completo e funcional com banco de dados, autenticação JWT e CRUD completo. Cada usuário só vê suas próprias tarefas graças ao filtro por user_id.
Possíveis Erros e Soluções
Causa: Token JWT não configurado ou expirado.
Solução: Faça login novamente para obter um novo token e configure na aba Auth do API Route Tester.
Causa: Módulo não foi ativado ou rota está incorreta.
Solução: Verifique se você clicou em "Ativar" após a análise do código. Confirme que a URL está correta (ex: /api/tarefa sem s no final).
Causa: Campo obrigatório não foi enviado ou está vazio.
Solução: Verifique se o campo titulo está presente no body e não está vazio.
Causa: Erro no código PHP ou problema com o banco de dados.
Solução: Abra o Terminal da IDE (Ctrl + `) e execute o arquivo com debug: F6. Verifique os logs de erro para identificar o problema.