Laravel Zero is a micro-framework that provides a starting point for your console application.
Combined with Neon's serverless Postgres database, you can create powerful CLI tools with persistent storage.
In this guide, we'll build a Todo CLI app that allows users to manage their tasks efficiently from the command line.
By the end of this tutorial, you'll have a fully functional Todo CLI app where users can add, list, update, and delete tasks. We'll also implement task prioritization and due dates to enhance the app's functionality.
Prerequisites
Before we start, make sure you have the following:
- PHP 8.1 or higher installed on your system
- Composer for managing PHP dependencies
- A Neon account for database hosting
- Basic knowledge of PHP and Laravel
Setting up the Project
Let's start by creating a new Laravel Zero project and setting up the necessary components.
Creating a New Laravel Zero Project
Open your terminal and run the following command to create a new Laravel Zero project:
composer create-project laravel-zero/laravel-zero todo-cli
cd todo-cliThis command creates a new Laravel Zero project in a directory named todo-cli and installs all the necessary dependencies.
Once the project is created, you can run the following command to rename the default app namespace to todo:
php application app:rename todoTo test the application, run:
php todo inspireThis command should display an inspirational quote from Laravel as a confirmation that the application is set up correctly.
Installing Required Add-Ons
Out of the box Laravel Zero provides a basic structure for CLI applications. Laravel Zero offers a variety of add-ons to extend the functionality of your CLI app which includes database support, testing, logging, file system, scheduler, and more.
To install the database package, run:
php todo app:install databaseThe
todocommand is the name of the executable file we defined in the previous step using theapp:renamecommand.
Additionally, we can install the fakerphp/faker package to generate sample data for testing later on:
composer require fakerphp/faker --devConfiguring the Database
The config/database.php file will include the database configuration for your application looking like this:
'connections' => [
// ... other connections ...
'neon' => [
'driver' => 'pgsql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'schema' => 'public',
'sslmode' => 'prefer',
],
],Rather than hardcoding the database credentials in the configuration file, we can use environment variables to store sensitive information securely. Create a .env file in the root of your project and add your Neon database credentials:
DB_CONNECTION=neon
DATABASE_URL=postgres://your-username:your-password@your-neon-hostname/your-database
DB_HOST=your-neon-hostname
DB_PORT=5432
DB_DATABASE=your-database
DB_USERNAME=your-username
DB_PASSWORD=your-passwordMake sure to replace the placeholders with your actual Neon database details.
Creating the Todo App Structure
Now that we have our basic setup, let's create the structure for our Todo CLI app, including models, migrations, and commands.
Creating the Task Model and Migration
Just like a standard Laravel application, we'll create a Task model to represent the tasks in our Todo app.
php todo make:model Task -mUpdate the migration file in database/migrations to define the structure of our tasks table:
public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description')->nullable();
$table->enum('status', ['pending', 'in_progress', 'completed'])->default('pending');
$table->enum('priority', ['low', 'medium', 'high'])->default('medium');
$table->date('due_date')->nullable();
$table->timestamps();
});
}This migration creates a tasks table with columns for the task title, description, status, priority, and due date.
Now, update the app/Task.php model to define the fillable attributes:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $fillable = ['title', 'description', 'status', 'priority', 'due_date'];
protected $casts = [
'due_date' => 'date',
];
}Run the migration to create the tasks table in your Neon database:
php todo migrateThis command will create the tasks table in your Neon database.
Creating Commands
Laravel Zero uses commands to define the CLI functionality. Let's create commands for adding, listing, updating, and deleting tasks.
Add Task Command
Create a new command to add tasks:
php todo make:command AddTaskCommandUpdate the app/Commands/AddTaskCommand.php the file to define the command signature and functionality:
<?php
namespace App\Commands;
use App\Task;
use Carbon\Carbon;
use LaravelZero\Framework\Commands\Command;
class AddTaskCommand extends Command
{
protected $signature = 'task:add {title} {--description=} {--priority=medium} {--due-date=}';
protected $description = 'Add a new task';
public function handle()
{
$task = Task::create([
'title' => $this->argument('title'),
'description' => $this->option('description'),
'priority' => $this->option('priority'),
'due_date' => $this->option('due-date') ? Carbon::parse($this->option('due-date')) : null,
]);
$this->info("Task added successfully! ID: {$task->id}");
}
}This command allows users to add a new task with a title, description, priority, and due date.
Rundown of the command signature:
{title}: The title of the task (required argument). Required arguments are passed without the--prefix.--description: The description of the task (optional option)--priority: The priority of the task (optional option with a default value ofmedium)--due-date: The due date of the task (optional option)
The handle method creates a new task record in the database with the provided details and displays a success message with the ID of the newly created task.
To verify that the command works, run:
php todo task:add "Complete Laravel Zero guide" --description="Write a guide on creating a Todo CLI app" --priority=high --due-date=2024-07-15You should see a success message with the ID of the newly created task.
List Tasks Command
Next, let's create a command to list tasks so we can view all tasks or filter them by status or priority:
php todo make:command ListTasksCommandUpdate the app/Commands/ListTasksCommand.php file:
<?php
namespace App\Commands;
use App\Task;
use LaravelZero\Framework\Commands\Command;
class ListTasksCommand extends Command
{
protected $signature = 'task:list {--status=} {--priority=}';
protected $description = 'List all tasks';
public function handle()
{
$query = Task::query();
if ($this->option('status')) {
$query->where('status', $this->option('status'));
}
if ($this->option('priority')) {
$query->where('priority', $this->option('priority'));
}
$tasks = $query->get();
$headers = ['ID', 'Title', 'Status', 'Priority', 'Due Date'];
$rows = $tasks->map(function ($task) {
return [
$task->id,
$task->title,
$task->status,
$task->priority,
$task->due_date ? $task->due_date->format('Y-m-d') : 'N/A',
];
});
$this->table($headers, $rows);
}
}Rundown of the command signature:
--status: Filter tasks by status (optional option)--priority: Filter tasks by priority (optional option)
The handle method retrieves tasks from the database based on the provided filters (if any) and displays them in a table format.
Now you can list all tasks or filter them by status or priority:
php todo task:listThe result will be displayed in a table format with columns for ID, Title, Status, Priority, and Due Date:
+----+-----------------------------+---------+----------+------------+
| ID | Title | Status | Priority | Due Date |
+----+-----------------------------+---------+----------+------------+
| 1 | Complete Laravel Zero guide | pending | high | 2024-07-15 |
+----+-----------------------------+---------+----------+------------+To filter tasks by status or priority, you can use the --status and --priority options:
php todo task:list --status=pendingUpdate Task Command
Now that we have the ability to add and list tasks, let's create a command to update tasks:
php todo make:command UpdateTaskCommandUpdate the app/Commands/UpdateTaskCommand.php file and define the command signature and functionality:
<?php
namespace App\Commands;
use App\Task;
use Carbon\Carbon;
use LaravelZero\Framework\Commands\Command;
class UpdateTaskCommand extends Command
{
protected $signature = 'task:update {id} {--title=} {--description=} {--status=} {--priority=} {--due-date=}';
protected $description = 'Update an existing task';
public function handle()
{
$task = Task::find($this->argument('id'));
if (!$task) {
$this->error("Task not found!");
return;
}
$updates = [];
if ($this->option('title')) {
$updates['title'] = $this->option('title');
}
if ($this->option('description')) {
$updates['description'] = $this->option('description');
}
if ($this->option('status')) {
$updates['status'] = $this->option('status');
}
if ($this->option('priority')) {
$updates['priority'] = $this->option('priority');
}
if ($this->option('due-date')) {
$updates['due_date'] = Carbon::parse($this->option('due-date'));
}
$task->update($updates);
$this->info("Task updated successfully!");
}
}Rundown of the command signature:
{id}: The ID of the task to update (required argument)--title: The new title of the task (optional option)--description: The new description of the task (optional option)--status: The new status of the task (optional option)--priority: The new priority of the task (optional option)--due-date: The new due date of the task (optional option)
The handle method retrieves the task by ID, checks if it exists, and updates the task with the provided details. It displays a success message if the task is updated successfully.
To update a task, run:
php todo task:update 1 --status=in_progressYou will get a Task updated successfully! message if the task is updated successfully.
To verify the update, list the tasks again:
php todo task:listNow you should see the updated status of the task:
+----+-----------------------------+-------------+----------+------------+
| ID | Title | Status | Priority | Due Date |
+----+-----------------------------+-------------+----------+------------+
| 1 | Complete Laravel Zero guide | in_progress | high | 2024-07-15 |
+----+-----------------------------+-------------+----------+------------+Try to also update other fields like the title, description, priority, or due date.
Delete Task Command
Finally to complete the basic CRUD operations, let's create a command to delete tasks:
php todo make:command DeleteTaskCommandUpdate the app/Commands/DeleteTaskCommand.php file:
<?php
namespace App\Commands;
use App\Task;
use LaravelZero\Framework\Commands\Command;
class DeleteTaskCommand extends Command
{
protected $signature = 'task:delete {id}';
protected $description = 'Delete a task';
public function handle()
{
$task = Task::find($this->argument('id'));
if (!$task) {
$this->error("Task not found!");
return;
}
$task->delete();
$this->info("Task deleted successfully!");
}
}Here the id argument is required to identify the task to be deleted.
The handle method retrieves the task by ID, checks if it exists, deletes the task, and displays a success message if the task is deleted successfully.
To delete a task, run:
php todo task:delete 1You should see a Task deleted successfully! message if the task is deleted successfully.
You can also try deleting a task that doesn't exist to see the error message:
php todo task:delete 2This will display a Task not found! error message.
Implementing Additional Features
To enhance our Todo CLI app, let's implement some additional features like task prioritization and due date reminders.
Task Prioritization
We've already included a priority field in our tasks table. Let's update the ListTasksCommand to sort tasks by priority:
Update the handle method in app/Commands/ListTasksCommand.php:
public function handle()
{
$query = Task::query();
if ($this->option('status')) {
$query->where('status', $this->option('status'));
}
if ($this->option('priority')) {
$query->where('priority', $this->option('priority'));
}
$tasks = $query->get();
$tasks = $query->orderByRaw("CASE
WHEN priority = 'high' THEN 1
WHEN priority = 'medium' THEN 2
WHEN priority = 'low' THEN 3
ELSE 4
END")->get();
// ... rest of the method
}This modification sorts the tasks by priority, with high priority tasks appearing first.
Due Date Reminders
Let's create a new command to show tasks that are due soon:
php todo make:command DueTasksCommandUpdate the app/Commands/DueTasksCommand.php to include the due date reminder functionality:
<?php
namespace App\Commands;
use App\Task;
use Carbon\Carbon;
use LaravelZero\Framework\Commands\Command;
class DueTasksCommand extends Command
{
protected $signature = 'task:due {days=7}';
protected $description = 'Show tasks due within the specified number of days';
public function handle()
{
$days = (int) $this->argument('days');
$dueDate = Carbon::now()->addDays($days);
$tasks = Task::where('status', '!=', 'completed')
->whereDate('due_date', '<=', $dueDate)
->orderBy('due_date')
->get();
if ($tasks->isEmpty()) {
$this->info("No tasks due within the next {$days} days.");
return;
}
$headers = ['ID', 'Title', 'Priority', 'Due Date', 'Days Left'];
$rows = $tasks->map(function ($task) {
$daysLeft = Carbon::now()->diffInDays($task->due_date, false);
return [
$task->id,
$task->title,
$task->priority,
$task->due_date->format('Y-m-d'),
$daysLeft,
];
});
$this->table($headers, $rows);
}
}This command shows tasks that are due within a specified number of days (default is 7) and aren't completed yet.
The handle method calculates the due date based on the provided number of days, retrieves tasks that are due within that period, and displays them in a table format with columns for ID, Title, Priority, Due Date, and Days Left.
To show tasks due within the next 14 days, run:
php todo task:due 14Testing the Todo CLI App
Now that we have implemented our Todo CLI app, let's try the complete workflow to ensure everything works as expected.
- Add a task:
php todo task:add "Complete Laravel Zero guide" --description="Write a guide on creating a Todo CLI app" --priority=high --due-date=2024-07-15- List all tasks:
php todo task:list- Update a task:
php todo task:update 1 --status=in_progress- Show due tasks:
php todo task:due 14- Delete a task:
php todo task:delete 1Building the Application
Rather than having to run the php todo command each time, Laravel Zero allows you to build your application into a single executable file. To do this, run:
php todo app:buildThis command will generate a standalone executable in the builds directory, which you can distribute and run on other systems without requiring PHP or Composer to be installed.
You will be prompted to choose the version of the application you want to build. Select the version that best suits your needs.
To run the built application, use the following command:
./builds/todoTo handle your database environment variables, you can create a .env file in the same directory as the built executable or set the environment variables directly in your system.
Conclusion
In this tutorial, we've built a fully functional Todo CLI app using Laravel Zero and Neon Postgres. We've implemented features such as adding, listing, updating, and deleting tasks, as well as task prioritization and due date reminders.
This implementation provides a solid foundation for a CLI-based task management system, but there are always ways to improve and expand its functionality:
- Implement task categories or tags
- Add support for recurring tasks
- Implement data export and import functionality
- Add user authentication for multi-user support
- Implement task dependencies (subtasks)
By combining the power of Laravel Zero and the scalability of Neon Postgres, you can quickly create efficient and powerful CLI applications that meet your specific needs.