Building a To Do List Application with BowPHP
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​
- 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.
- Configure your application:
- Edit the
.env.jsonfile to configure the database connection (for example MySQL).
{
"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​
- Create the database:
Run this MySQL command to create a database:
CREATE DATABASE todolist_db;
- 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.
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​
- Create a
Taskmodel:
Add a database model
php bow add:model Task
You will see the app/Models/Task.php file in your project.
namespace App\Models;
use Bow\Database\Barry\Model;
class Task extends Model
{
//
}
- Create a
TaskControllercontroller:
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']);
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.
<!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>
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:
- Task filtering: Add filters to view completed/incomplete tasks
- Due date: Add a
due_datecolumn to manage deadlines - Categories: Create a category or tag system to organize tasks
- Authentication: Implement a login system so that each user has their own tasks
- REST API: Turn the application into an API for use with a JavaScript frontend (React, Vue.js)
- Search: Add a search bar to filter tasks
- 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
- 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.