Skip to main content

Building a To Do List Application with BowPHP

· 6 min read
Franck DAKIA
Principal maintainer

Building a To Do List application with BowPHP, a lightweight PHP framework, is a great idea. Here is a detailed plan to get there:

Step 1: Installing BowPHP​

  1. Install BowPHP:

Make sure Composer is installed on your machine, then use the following command to install BowPHP:

composer create-project bowphp/app todolist

This will create a new BowPHP application named todolist.

  1. Configure your application:
  • Edit the .env.json file to configure the database connection (for example MySQL).
.env.json
{
"DB_DEFAULT": "mysql",
"DB_HOSTNAME": "127.0.0.1",
"DB_USERNAME": "username",
"DB_PASSWORD": "password",
"DB_DBNAME": "todolist_db",
"DB_PORT": 3306
}

Step 2: Configure the database​

  1. Create the database:

Run this MySQL command to create a database:

CREATE DATABASE todolist_db;
  1. Create a table for tasks:

To add a new table, you can do so with the following command:

php bow add:migration create-tasks-table

You will get a new file in the database/migrations folder.

database/migrations/Version1000000000CreateTasksTable.php

use Bow\Database\Migration\Migration;
use Bow\Database\Migration\SQLGenerator as Table;

class Version1000000000CreateTasksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$this->create('tasks', function (Table $table) {
$table->addPrimary('id');
$table->addString('title');
$table->addBoolean('completed', ['default' => false]);
$table->addTimestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function rollback()
{
$this->dropIfExists('tasks');
}
}

Then, run the migration:

php bow migrate

Step 3: Create the model and controller​

  1. Create a Task model:

Add a database model

php bow add:model Task

You will see the app/Models/Task.php file in your project.

app/Models/Task.php
namespace App\Models;

use Bow\Database\Barry\Model;

class Task extends Model
{
//
}
  1. Create a TaskController controller:
app/Controllers/TaskController.php
namespace App\Controllers;

use App\Models\Task;
use Bow\Http\Request;

class TaskController
{
public function index()
{
$tasks = Task::all();

return view('tasks.index', compact('tasks'));
}

public function store(Request $request)
{
// Data validation
$request->validate([
'title' => 'required|min:3|max:255'
]);

Task::create([
'title' => $request->get('title'),
'completed' => false
]);

return redirect('/')->withFlash('success', 'Tâche ajoutée avec succès !');
}

public function update(int $id)
{
$task = Task::find($id);

if (!$task) {
return redirect('/')->withFlash('error', 'Tâche introuvable.');
}

$task->update(['completed' => !$task->completed]);

return redirect('/')->withFlash('success', 'Tâche mise à jour !');
}

public function destroy(Request $request, int $id)
{
$task = Task::find($id);

if (!$task) {
return redirect('/')->withFlash('error', 'Tâche introuvable.');
}

$task->delete();

return redirect('/')->withFlash('success', 'Tâche supprimée !');
}
}

Step 4: Configure the routes​

In the routes/app.php file, add the following routes:

use App\Controllers\TaskController;

$app->get('/', [TaskController::class, 'index']);
$app->post('/tasks', [TaskController::class, 'store']);
$app->put('/tasks/{id}', [TaskController::class, 'update']);
$app->delete('/tasks/{id}', [TaskController::class, 'destroy']);
Note on HTTP methods

BowPHP automatically handles method spoofing via the _method field in forms. This makes it possible to use PUT and DELETE even though HTML natively supports only GET and POST.

Step 5: Create the views​

Create the template file templates/tasks.tintin.php and add the following content to it.

templates/tasks.tintin.php
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo List - BowPHP</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 600px;
margin: 0 auto;
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
h1 {
color: #333;
margin-bottom: 2rem;
text-align: center;
}
.add-form {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
}
input[type="text"] {
flex: 1;
padding: 0.75rem;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 1rem;
}
input[type="text"]:focus {
outline: none;
border-color: #667eea;
}
button {
padding: 0.75rem 1.5rem;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
transition: background 0.3s;
}
button:hover { background: #5568d3; }
.task-list { list-style: none; }
.task-item {
display: flex;
align-items: center;
padding: 1rem;
margin-bottom: 0.5rem;
background: #f8f9fa;
border-radius: 8px;
gap: 0.75rem;
}
.task-item.completed {
opacity: 0.6;
text-decoration: line-through;
}
.task-title { flex: 1; }
.btn-icon {
padding: 0.5rem;
background: transparent;
border: none;
cursor: pointer;
font-size: 1.25rem;
}
.flash-message {
padding: 1rem;
margin-bottom: 1rem;
border-radius: 8px;
font-weight: 500;
}
.flash-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.flash-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
</style>
</head>
<body>
<div class="container">
<h1>📝 Ma Todo List</h1>

#if(flash()->has('success'))
<div class="flash-message flash-success">
{{ flash()->get('success') }}
</div>
#endif

#if(flash()->has('error'))
<div class="flash-message flash-error">
{{ flash()->get('error') }}
</div>
#endif

<form method="POST" action="/tasks" class="add-form">
{{ csrf_field() }}
<input type="text" name="title" placeholder="Ajouter une nouvelle tâche..." required>
<button type="submit">Ajouter</button>
</form>

<ul class="task-list">
#foreach ($tasks as $task)
<li class="task-item {{ $task->completed ? 'completed' : '' }}">
<form method="POST" action="/tasks/{{ $task->id }}" style="display: inline;">
{{ csrf_field() }}
<input type="hidden" name="_method" value="PUT">
<button type="submit" class="btn-icon">
{{ $task->completed ? '✅' : '⬜' }}
</button>
</form>
<span class="task-title">{{ $task->title }}</span>
<form method="POST" action="/tasks/{{ $task->id }}" style="display: inline;">
{{ csrf_field() }}
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn-icon" onclick="return confirm('Supprimer cette tâche ?')">❌</button>
</form>
</li>
#endforeach
</ul>

#if(count($tasks) === 0)
<p style="text-align: center; color: #999; padding: 2rem;">Aucune tâche. Ajoutez-en une ci-dessus !</p>
#endif
</div>
</body>
</html>
CSRF Security

Note the use of {{ csrf_field() }} in all forms. This is built-in CSRF protection in BowPHP that prevents Cross-Site Request Forgery attacks.

Step 6: Run the application​

Start the BowPHP server:

php bow run:server

Access your application at http://localhost:5000.

Step 7: Possible improvements​

Here are a few ideas to improve your application:

  1. Task filtering: Add filters to view completed/incomplete tasks
  2. Due date: Add a due_date column to manage deadlines
  3. Categories: Create a category or tag system to organize tasks
  4. Authentication: Implement a login system so that each user has their own tasks
  5. REST API: Turn the application into an API for use with a JavaScript frontend (React, Vue.js)
  6. Search: Add a search bar to filter tasks
  7. Priorities: Add priority levels (high, medium, low)

Deployment​

To deploy your application to production:

# 1. Optimize the autoloader
composer dump-autoload --optimize

# 2. Configure the production environment in .env.json
{
"APP_ENV": "production",
"APP_DEBUG": false,
// ... other configs
}

# 3. Configure your web server (Apache/Nginx)
# The entry point must point to public/index.php
Production Security
  • Disable debug mode (APP_DEBUG=false)
  • Use strong passwords for the database
  • Configure HTTPS on your server
  • Keep BowPHP and its dependencies up to date
  • Always validate user input

Useful resources​

Is something missing?

If you run into problems with the documentation or have suggestions to improve the documentation or the project in general, please open an issue for us, or send a tweet mentioning the Twitter account @bowframework or directly on github.