Building a Task Reminder With Laravel and MongoDB
Avalie esse Tutorial
The popularity of Laravel has improved recently as more developers have started to adopt the technology. Laravel has an easy-to-understand syntax that boosts productivity by letting the developer focus on the core features of their application and not bother with repetitive tasks like authentication and sending emails. It has a very vibrant community of users and great learning materials.
Freshly created Laravel applications are configured to use relational databases. The purpose of this tutorial is to show how to use MongoDB in a Laravel application. We will build a simple task reminder system to achieve this.
The following tools and technologies are required to follow along effectively with this tutorial.
- A free MongoDB Atlas cluster
- PHP and Composer
- Basic knowledge of the Laravel framework
- Familiarity with MongoDB and NoSQL databases
To work with MongoDB, we need to configure our development environment. We need to ensure that all the necessary development dependencies are installed and configured properly. Make sure you have the following installed.
To get started building our reminder system, the first thing to do is create a new Laravel project. We can do so using the composer with the command below:
1 composer create-project laravel/laravel LaravelMongodbProject 2 cd LaravelMongodbProject
A freshly created Laravel project when installed comes with default configurations for relational databases like MySql and PostgreSQL. MongoDB does not work in Laravel by default. We need to install the Laravel MongoDB package and also do a little configuration in config/database.php. Proceed to install the package using the command below:
1 composer require mongodb/laravel-mongodb
Once the installation of the Laravel MongoDB package is completed, the next step is to add our MongoDB database connection to our config/database.php file to complete the configuration. Copy the code below and paste it in the
connections
array that contains configurations for other database types.1 return [ 2 'connections' => [ 3 'mongodb' => [ 4 'driver' => 'mongodb', 5 'dsn' => env('MONGODB_URI'), 6 'database' => 'YOUR_DATABASE_NAME', 7 ], 8 //You can keep other existing connections 9 ],
Let's take a moment to explain. The
dsn
value is obtained from the .env file. In your .env file, create a value for 'MONGODB_URI
and set it to the value of your MongoDB Atlas connection string, like below:1 MONGODB_URI="<<MONGODB_ATLAS_CONNECTION_STRING>>" 2 DB_CONNECTION=mongodb
We have installed and configured our application to work with MongoDB. Let's proceed to authentication. Laravel simplifies the implementation of authentication by providing packages like Laravel Breeze, Laravel Fortify, and Laravel Jetstream. In this tutorial, we will use Laravel Breeze for our authentication. We need to install it using Composer with the command below:
1 composer require laravel/breeze --dev
Once the installation is complete, proceed by running the next set of commands.
1 php artisan key:generate 2 3 php artisan breeze:install 4 php artisan migrate 5 6 php artisan db:seed 7 npm install 8 npm run dev
This will prompt you to choose your preferred stack and testing package like the sample below. For the purpose of this article, we will select the first option (Blade and Alpine).
1 ┌ Which Breeze stack would you like to install? ───────────────┐ 2 │ > ● Blade with Alpine │ 3 │ ○ Livewire (Volt Class API) with Alpine │ 4 │ ○ Livewire (Volt Functional API) with Alpine │ 5 │ ○ React with Inertia │ 6 │ ○ Vue with Inertia │ 7 │ ○ API only
Afterward, it will add authentication views, routes, controllers, and other related resources to your application.
At this point, let's serve the project using Laravel's built-in server and confirm that everything works properly. Serve your project using the command below:
1 php artisan serve
The project should be served at
127.0.0.1:8000
. In case the port 8000
is already in use, Laravel will switch to a new available port. If everything was done right, your screen should look like the image belowTo make sure that the Laravel MongoDB package was configured properly, let's create a route to ping our MongoDB cluster. Add the following to route/web.php.
1 Route::get('/ping', function (Request $request) { 2 $connection = DB::connection('mongodb'); 3 try { 4 $connection->command(['ping' => 1]); 5 $msg = 'MongoDB is accessible!'; 6 } catch (Exception $e) { 7 $msg = 'You are not connected to MongoDB. Error: ' . $e->getMessage(); 8 } 9 return ['msg' => $msg]; 10 });
Visit this route in your browser. Your screen should look like the image below, if everything was done right:
Let's create our model and controller for the task scheduling feature. Use the command below to do so:
1 php artisan make:model Task --resource --controller
The command above will create the
Task
model in the app/Models directory and the TaskController
in the app/Http/Controllers directory with resource methods.Let's create a route for the
TaskController
. Navigate to routes/web.php and add the following to the file.1 use App\Http\Controllers\TaskController; 2 Route::resource('tasks', TaskController::class)->middleware('auth');
Next, let's modify the content of the task model to our needs. Navigate to app/Models/Task.php and replace the content with the following:
1 2 namespace App\Models; 3 use MongoDB\Laravel\Eloquent\Model; 4 class Task extends Model 5 { 6 protected $connection = 'mongodb'; 7 protected $table = 'tasks'; 8 protected $fillable = [ 9 'title', 'description', 'due_date', 'email', 'reminder_time', 'last_notification_date' 10 ]; 11 }
The code above is our task model.
- The
use MongoDB\Laravel\Eloquent\Model
statement after the namespace is specific to MongoDB models. It overrides the Eloquent features implemented with SQL, using MongoDB queries. - The
protected $table = 'tasks'
is optional. It is the name of the MongoDB collection that is used to tore the documents from this model. - The
protected $fillable = ['title', 'description', 'due_date', 'email', 'reminder_time']
specifies the mass assignable properties.
One of the unique features of MongoDB is that it doesn't need migrations like relational databases do. This means you can add new fields directly to your documents without having to update the model or create migrations. This is particularly helpful in handling dynamic data.
Next, let's modify our controller. Navigate to app/Http/Controllers/TaskController.php and update the content with the code below:
1 2 namespace App\Http\Controllers; 3 use App\Models\Task; 4 use Carbon\Carbon; 5 use Illuminate\Http\Request; 6 class TaskController extends Controller 7 { 8 /** 9 * Display a listing of the resource. 10 */ 11 public function index() 12 { 13 $tasks = Task::where('email', auth()->user()->email)->get(); 14 return view('tasks.index', compact('tasks')); 15 } 16 17 /** 18 * Show the form for creating a new resource. 19 */ 20 public function create() 21 { 22 return view('tasks.create'); 23 } 24 25 /** 26 * Store a newly created resource in storage. 27 */ 28 public function store(Request $request) 29 { 30 $request->validate([ 31 'title' => 'required|string|max:255', 32 'description' => 'nullable|string', 33 'due_date' => 'required|date', 34 'reminder_time' => 'required|date', 35 ]); 36 37 $data = $request->all(); 38 $data['due_date'] = Carbon::parse($request->due_date); 39 $data['reminder_time'] = Carbon::parse($request->reminder_time); 40 $data['email'] = auth()->user()->email; 41 42 $data['last_notification_date'] = null; 43 44 Task::create($data); 45 46 return redirect()->route('tasks.index')->with('success', 'Task created successfully.'); 47 } 48 49 /** 50 * Display the specified resource. 51 */ 52 public function show(string $id) 53 { 54 // 55 } 56 57 /** 58 * Show the form for editing the specified resource. 59 */ 60 public function edit(string $id) 61 { 62 $tasks = Task::where('id', $id)->get(); 63 return view('tasks.edit', ['tasks' => $tasks]); 64 } 65 66 /** 67 * Update the specified resource in storage. 68 */ 69 public function update(Request $request, string $id) 70 { 71 $task = Task::findOrFail($id); 72 $data = $request->all(); 73 74 $data['due_date'] = Carbon::parse($request->due_date)->format('Y-m-d H:i:s'); 75 $data['reminder_time'] = Carbon::parse($request->reminder_time)->format('Y-m-d H:i:s'); 76 $task->update($data); 77 78 return redirect()->route('tasks.index')->with('success', 'Task updated successfully.'); 79 } 80 81 /** 82 * Remove the specified resource from storage. 83 */ 84 public function destroy(string $id) 85 { 86 $task = Task::findOrFail($id); 87 $task->delete(); 88 Return redirect()->route('tasks.index')->with('success', 'Task deleted successfully.'); 89 } 90 }
Our newly created
TaskController
above contains code that handles the CRUD operations of our task model. The index method retrieves all tasks belonging to the logged-in user and sends them to the index.blade.php file to be displayed on the front end.The create method returns the form view for creating a new task, while the store method validates the input, assigns the logged-in user's email to the task, and saves it to the database.
For updates, the edit method retrieves the specific task to be edited and displays it in an edit form. When this form is submitted, it calls the update method which saves the edited task in our MongoDB task collection.
The destroy method deletes a specific task. Each operation redirects back to the task's list with a success message for user feedback.
Let's create view files for our task scheduler. In the resources/views directory, create a folder named /tasks and create the following files in it:
- create.blade.php
- edit.blade.php
- index.blade.php
Navigate to resources/views/create.blade.php and replace the content of the page with the following:
1 <x-app-layout> 2 <x-slot name="header"> 3 <h2 class="font-semibold text-xl text-gray-800 leading-tight"> 4 {{ __('Tasks') }} 5 </h2> 6 </x-slot> 7 8 <div class="py-12"> 9 <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> 10 <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> 11 <div class="container mx-auto p-4"> 12 <h2 class="text-2xl font-bold mb-4">Create New Task</h2> 13 <form action="{{ route('tasks.store') }}" method="POST"> 14 @csrf 15 <div class="mb-4"> 16 <label for="title" class="block text-gray-700">Title:</label> 17 <input type="text" name="title" id="title" required class="border border-gray-300 p-2 w-full" value="{{ old('title') }}"> 18 @error('title') 19 <p class="text-red-500">{{ $message }}</p> 20 @enderror 21 </div> 22 <div class="mb-4"> 23 <label for="description" class="block text-gray-700">Description:</label> 24 <textarea name="description" id="description" class="border border-gray-300 p-2 w-full">{{ old('description') }}</textarea> 25 </div> 26 <div class="mb-4"> 27 <label for="due_date" class="block text-gray-700">Due Date:</label> 28 <input type="date" name="due_date" id="due_date" required class="border border-gray-300 p-2 w-full" value="{{ old('due_date') }}"> 29 @error('due_date') 30 <p class="text-red-500">{{ $message }}</p> 31 @enderror 32 </div> 33 <div class="mb-4"> 34 <label for="reminder_time" class="block text-gray-700">Reminder Time:</label> 35 <input type="datetime-local" name="reminder_time" id="reminder_time" class="border border-gray-300 p-2 w-full" value="{{ old('reminder_time') }}"> 36 </div> 37 <button type="submit" class="bg-green-600 text-white text-gray-399 p-2 border rounded">Create Task</button> 38 </form> 39 </div> 40 </div> 41 </div> 42 </div> 43 </x-app-layout> 44
In the code above, we added
HTML
for the create form. The form contains text input for title
and description
and date and time input for due date
and reminder time
. If your code is right, your screen should look like the image below.Do the same for edit.blade.php. Navigate to resources/views/edit.blade.php and add the code below to the content of the page:
1 <x-app-layout> 2 <x-slot name="header"> 3 <h2 class="font-semibold text-xl text-gray-800 leading-tight"> 4 {{ __('Edit Task') }} 5 </h2> 6 </x-slot> 7 8 <div class="py-12"> 9 <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> 10 <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> 11 <div class="p-6 text-gray-900"> 12 @foreach($tasks as $task) 13 <form action="{{ route('tasks.update', $task->id) }}" method="POST"> 14 @csrf 15 @method('PUT') 16 <div class="mb-4"> 17 <label for="title" class="block text-gray-700">Title:</label> 18 <input type="text" name="title" id="title" required class="border border-gray-300 p-2 w-full" value="{{ old('title', $task->title) }}"> 19 @error('title') 20 <p class="text-red-500">{{ $message }}</p> 21 @enderror 22 </div> 23 <div class="mb-4"> 24 <label for="description" class="block text-gray-700">Description:</label> 25 <textarea name="description" id="description" class="border border-gray-300 p-2 w-full">{{ old('description', $task->description) }}</textarea> 26 </div> 27 <div class="mb-4"> 28 <label for="due_date" class="block text-gray-700">Due Date:</label> 29 <input type="date" name="due_date" id="due_date" required class="border border-gray-300 p-2 w-full" value="{{ old('due_date', $task->due_date) }}"> 30 @error('due_date') 31 <p class="text-red-500">{{ $message }}</p> 32 @enderror 33 </div> 34 <div class="mb-4"> 35 <label for="reminder_time" class="block text-gray-700">Reminder Time:</label> 36 <input type="datetime-local" name="reminder_time" id="reminder_time" class="border border-gray-300 p-2 w-full" value="{{ old('reminder_time', $task->reminder_time) }}"> 37 </div> 38 <button type="submit" class="bg-blue-500 text-white p-2 rounded">Update Task</button> 39 </form> 40 @endforeach 41 </div> 42 </div> 43 </div> 44 </div> 45 </x-app-layout>
The
edit
contains the same input fields as the create
form above. It is loaded with the data of the task currently being edited.Lastly, navigate to resources/views/index.blade.php and replace the content with the code below:
1 <x-app-layout> 2 <x-slot name="header"> 3 <h2 class="font-semibold text-xl text-gray-800 leading-tight"> 4 {{ __('Tasks') }} 5 </h2> 6 </x-slot> 7 8 <div class="py-12"> 9 <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> 10 <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> 11 <div class="p-6 text-gray-900"> 12 <div class="mb-2"> 13 <a href="{{ route('tasks.create') }}" class="p-2 border mb-4">Create New Task</a> 14 </div> 15 <ul class="mt-4"> 16 @foreach ($tasks as $task) 17 <div class="mt-2"> 18 <hr> 19 </div> 20 <li> 21 <h1 class="text-2xl"> 22 <strong>{{ $task->title }}</strong> - Due: {{ $task->due_date }} 23 </h1> 24 <p class="text-gray-600"> 25 {{ $task->description }} 26 </p> 27 <div class="flex gap-2 mt-4"> 28 <div class="p-2 text-white bg-gray-700"> 29 <a href="{{ route('tasks.edit', $task->id) }}">Edit</a> 30 </div> 31 <div class="p-2 text-white bg-red-700 rounded"> 32 <form action="{{ route('tasks.destroy', $task->id) }}" method="POST" style="display:inline;"> 33 @csrf 34 @method('DELETE') 35 <button type="submit">Delete</button> 36 </form> 37 </div> 38 </div> 39 </li> 40 @endforeach 41 </ul> 42 </div> 43 </div> 44 </div> 45 </div> 46 </x-app-layout>
In the code above, we have a link to the
create
form. It also loops through all the reminders and displays those belonging to this user.Add route
Next, we need to add a link to the task feature from our application navigation. Open resouces/views/layouts/navigation.blade.php and add the following line of code just after the dashboard navigation link.
1 //...existing code ... 2 <div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex"> 3 <x-nav-link :href="route('tasks.index')":active="request()->routeIs('tasks.index')"> 4 {{ __('Tasks') }} 5 </x-nav-link> 6 </div>
At this point, we should be able to test the CRUD operation of our task management system. Ensure that everything works correctly before moving on to the next section.
Let's proceed to implementing the reminder system for due tasks. To do this we will:
- Create a custom artisan command to send reminders through email.
- Register the command for automatic scheduling. Let's create a custom artisan command using the command below:
1 php artisan make:command SendTaskReminders
Once the command is completed, update the content of app/Console/Commands/SendTaskReminders.php with the code below:
1 2 3 namespace App\Console\Commands; 4 5 use App\Models\Task; 6 use Carbon\Carbon; 7 use Illuminate\Console\Command; 8 use Illuminate\Support\Facades\Mail; 9 10 class SendTaskReminders extends Command 11 { 12 /** 13 * The name and signature of the console command. 14 * 15 * @var string 16 */ 17 protected $signature = 'app:send-task-reminders'; 18 19 /** 20 * The console command description. 21 * 22 * @var string 23 */ 24 protected $description = 'Command description'; 25 26 /** 27 * Execute the console command. 28 */ 29 public function handle() 30 { 31 $now = Carbon::now(); 32 $upcomingTasks = Task::where('last_notification_date', null)->get(); 33 34 $upcomingTasks = Task::where('last_notification_date', null) 35 36 ->where('reminder_time', '>=', $now->clone()->subMinutes(10)) 37 38 ->get(); 39 40 foreach ($upcomingTasks as $task) { 41 $emailBody = <<<EOF 42 Hello, 43 This is a reminder for your task: 44 Title: {$task->title} 45 Description: {$task->description} 46 Due Date: {$task->due_date} 47 Please make sure to complete it on time! 48 Regards, 49 Your Task Reminder App 50 EOF; 51 52 Mail::raw($emailBody, function ($message) use ($task) { 53 $message->to($task->email) 54 ->subject("Task Reminder: {$task->title}"); 55 }); 56 57 $task->last_notification_date = $now; 58 $task->save(); 59 $this->info("Reminder email sent for task: {$task->title}"); 60 } 61 62 return self::SUCCESS; 63 } 64 }
The main logic of our custom artisan command is written in the
handle()
method. From the code above, we get the current timestamp using Carbon::now()
.Next, we query the database to get all the tasks where
reminder_time
is less than or equal to the current time (the value of $now) and where reminder_time
is greater than or equal to 10 minutes before the time, with this line of code: 'reminder_time','>=',$now->clone()->subMinutes(10)
. In MongoDB, all dates are stored in UTC. Even if your server uses a different timezone, you don't need to change it.To throw more light, let's assume that the current time is $now = 2024-10-22 15:00:00. The task reminder query fetches all tasks between 2024-10-22 14:50:00 and 2024-10-22 15:00:00. We fetch all tasks that are due in 10 minutes.
Then, we loop through the result and send reminders to the user emails of tasks that will be due in the next 10 minutes.
For the task scheduler to work properly, you must configure your application to send emails. Mailtrap.io is a great tool for testing email sending. Get a detailed explanation of how to configure your application to send emails using Mailtrap.
To wrap it all up, we need to schedule the artisan command created above to run every minute. Think of this as a way to automatically run
php artisan app:send-task-reminders
every minute.There are different approaches to scheduling a task in Laravel. In this tutorial, we are working with Laravel version 11.9, which is the most recent version at the time of this writing. To proceed, navigate to routes/console.php and add the following:
1 //...existing code ... 2 Schedule::command('app:send-task-reminders')->everyMinute();
To test that this works, run the command below:
1 php artisan schedule:run
On a production server, you will need to configure a cron job to run the
php artisan schedule:run
command at regular intervals.In a Linux- or Unix-based server, you can open your cron configuration file using the command below:
1 crontab -e
Add the code below into the cron configuration tab:
1 * * * * * /usr/bin/php /path-to-your-project/artisan schedule:run >> /dev/null 2>&1
For this to work properly, replace
/usr/bin/php
with the path to your PHP binary and /path-to-your-project
with the full path to your Laravel project on your server, then save and exit.You can verify that everything works fine by typing this command below:
1 crontab -l
We have come to the end of this tutorial; great job if you followed along. Now, for a recap, in this tutorial, we walked through the process of creating a task scheduler in Laravel and MongoDB. Some of the key implementations were:
- Configuring a Laravel project to work with MongoDB.
- Implementing CRUD features for our tasks scheduler.
- Creating a custom Laravel artisan command for our reminder system.
- Scheduling a task to run the artisan command in intervals.
- Walking through the steps to configure the cron job on a Linux-based server.
Find the project on GitHub. Feel free to clone it, sign up for MongoDB Atlas, and customize it to your specific needs. For more support, join the MongoDB Developer Community.
Principais comentários nos fóruns
Ainda não há comentários sobre este artigo.