In this tutorial, we will build a complete RESTful API in Laravel to manage tasks, taking advantage of the power of Laravel, a PHP framework developed with developer productivity in mind. Laravel is written and maintained by Taylor Otwell, and it is a highly opinionated framework designed to save developers' time by favoring convention over configuration.
Before we begin, let's understand what a RESTful API is. REST, which stands for REpresentational State Transfer, is an architectural style for network communication between applications, relying on a stateless protocol (usually HTTP) for interaction.
In RESTful APIs, we use HTTP verbs as actions, and the endpoints represent the resources being acted upon. Each HTTP verb has a specific semantic meaning:
- GET: retrieve resources
- POST: create resources
- PUT: update resources
- DELETE: delete resources
Now, let's get started with setting up the project and creating automated tests that will be part of our task management API in Laravel.
Step 1: With PHP and Composer installed, let's start our task API project.
Run the following command to create a new Laravel project:
composer create-project laravel/laravel task-api
After creating the project, navigate to the folder and run the following commands in the terminal:
cd task-api
code .
Alternatively, you can open VS Code and then select the project.
Now let's configure the environment variables so our project can access the database!
In the .env
file, set your database configurations:
DB_DATABASE=task_api
DB_USERNAME=your_username
DB_PASSWORD=your_password
After configuring the database, let's create our first migration, which is a way to version the database. Laravel uses migrations, just like many other frameworks in the market.
To create the tasks
table, run:
php artisan make:migration create_tasks_table --create=tasks
In the migration file (database/migrations/xxxx_xx_xx_create_tasks_table.php
), define the table structure:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description')->nullable();
$table->boolean('completed')->default(false);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('tasks');
}
};
Run the migration to create the table:
php artisan migrate
Step 2: Laravel is a framework that works well with the MVC model. Following this pattern, let's create the model and controller for our tasks.
Run the following commands:
php artisan make:model Task
php artisan make:controller TaskController --api
After creating the model (our class responsible for database access) for the Task
, we need to add some values to the fillable
property. This allows us to populate these fields when creating or editing a task (app/Models/Task.php
):
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
use HasFactory;
protected $fillable = ['title', 'description', 'completed'];
}
Step 3: After creating the model and controller (which we will explore soon), we need to define the routes for our tasks
resource. Laravel makes this process simple and efficient.
In the routes/api.php
file, add the routes for the TaskController
:
<?php
use App\Http\Controllers\TaskController;
use Illuminate\Support\Facades\Route;
Route::apiResource('tasks', TaskController::class);
Step 4: Now, let's discuss the controller. After creating the model and configuring the properties that can be inserted and updated in the database, we’ll move on to the implementation of the TaskController
. This controller is responsible for handling all user requests based on the defined routes.
In the TaskController
, we’ll implement the basic CRUD methods:
<?php
namespace App\Http\Controllers;
use App\Models\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index()
{
$tasks = Task::all();
return response()->json($tasks, 200);
}
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string'
]);
$task = Task::create($request->all());
return response()->json($task, 201);
}
public function show(Task $task)
{
return response()->json($task, 200);
}
public function update(Request $request, Task $task)
{
$request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'completed' => 'boolean'
]);
$task->update($request->all());
return response()->json($task, 201);
}
public function destroy(Task $task)
{
$task->delete();
return response()->json(null, 204);
}
}
Step 5: After creating the complete MVC for our task resource, let's test the API endpoints. You can do this using VS Code or tools like Insomnia or Postman.
Now, we’ll test each endpoint manually using a VS Code extension called REST Client (https://marketplace.visualstudio.com/items?itemName=humao.rest-client).
After installing the extension, create an .http
file in your project folder with the following content:
### Create New Task
POST http://127.0.0.1:8000/api/tasks HTTP/1.1
content-type: application/json
Accept: application/json
{
"title": "Study Laravel"
}
### Show Tasks
GET http://127.0.0.1:8000/api/tasks HTTP/1.1
content-type: application/json
Accept: application/json
### Show Task
GET http://127.0.0.1:8000/api/tasks/1 HTTP/1.1
content-type: application/json
Accept: application/json
### Update Task
PUT http://127.0.0.1:8000/api/tasks/1 HTTP/1.1
content-type: application/json
Accept: application/json
{
"title": "Study Laravel and Docker",
"description": "We are studying!",
"completed": false
}
### Delete Task
DELETE http://127.0.0.1:8000/api/tasks/1 HTTP/1.1
content-type: application/json
Accept: application/json
This file lets you send requests directly from VS Code using the REST Client extension, making it easy to test each route in your API.
Step 6: After creating everything, the only way to ensure that our API is functioning correctly is through tests. For this example, we will test the API endpoints to guarantee that, but first, let's create something to help us populate the database for testing.
First, create a factory for the Task
model:
php artisan make:factory TaskFactory
Then, define the factory like this:
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
class TaskFactory extends Factory
{
public function definition(): array
{
return [
'title' => fake()->sentence(),
'description' => fake()->paragraph(),
'completed' => false,
];
}
}
Here’s my PHPUnit configuration:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing" />
<env name="BCRYPT_ROUNDS" value="4" />
<env name="CACHE_DRIVER" value="array" />
<env name="DB_CONNECTION" value="sqlite" />
<env name="DB_DATABASE" value=":memory:" />
<env name="MAIL_MAILER" value="array" />
<env name="PULSE_ENABLED" value="false" />
<env name="QUEUE_CONNECTION" value="sync" />
<env name="SESSION_DRIVER" value="array" />
<env name="TELESCOPE_ENABLED" value="false" />
</php>
</phpunit>
Now, let's test our API! You can create the test with the following command, Laravel makes it very easy to develop and test our applications:
php artisan make:test TaskApiTest
In the tests/Feature/TaskApiTest.php
file, implement the tests:
<?php
namespace Tests\Feature;
use App\Models\Task;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class TaskApiTest extends TestCase
{
use RefreshDatabase;
public function test_can_create_task(): void
{
$response = $this->postJson('/api/tasks', [
'title' => 'New Task',
'description' => 'Task Description',
'completed' => false,
]);
$response->assertStatus(201);
$response->assertJson([
'title' => 'New Task',
'description' => 'Task Description',
'completed' => false,
]);
}
public function test_can_list_tasks()
{
Task::factory()->count(3)->create();
$response = $this->getJson('/api/tasks');
$response->assertStatus(200);
$response->assertJsonCount(3);
}
public function test_can_show_task()
{
$task = Task::factory()->create();
$response = $this->getJson("/api/tasks/{$task->id}");
$response->assertStatus(200);
$response->assertJson([
'title' => $task->title,
'description' => $task->description,
'completed' => false,
]);
}
public function test_can_update_task()
{
$task = Task::factory()->create();
$response = $this->putJson("/api/tasks/{$task->id}", [
'title' => 'Update Task',
'description' => 'Update Description',
'completed' => true,
]);
$response->assertStatus(201);
$response->assertJson([
'title' => 'Update Task',
'description' => 'Update Description',
'completed' => true,
]);
}
public function test_can_delete_task()
{
$task = Task::factory()->create();
$response = $this->deleteJson("/api/tasks/{$task->id}");
$response->assertStatus(204);
$this->assertDatabaseMissing('tasks', ['id' => $task->id]);
}
}
To run the tests, just type the following in the terminal:
php artisan test
I hope you liked it! Read through it carefully, and if needed, read it again, coding part by part to understand everything. If you're a beginner, you might not fully grasp everything at first, but if you put in the effort and go through the tutorial slowly, I'm sure you'll learn a lot.
Top comments (0)