Hello! In this tutorial, we’ll be building a complete REST API in Laravel to manage tasks. I'll guide you through the basic steps, from setting up the project to creating automated tests.
Step 1: Project Setup
Create a new Laravel project:
composer create-project laravel/laravel task-api
cd task-api
code .
Configure the database:
In the .env
file, set your database configurations:
DB_DATABASE=task_api
DB_USERNAME=your_username
DB_PASSWORD=your_password
Generate the tasks table:
Run the command to create a new migration for the tasks
table:
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: Creating the Model and Controller
Create the model and controller for the task:
php artisan make:model Task
php artisan make:controller TaskController --api
Define the Task
model (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: Defining API Routes
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: Implementing CRUD in TaskController
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: Testing Endpoints (VS Code)
Now we’ll test each endpoint manually, using a VS Code extension called REST Client (https://marketplace.visualstudio.com/items?itemName=humao.rest-client). If you prefer, you can also use Insomnia or Postman!
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: Testing the API
Next, let’s create tests to ensure each route works as expected.
First, create a factory for the Task model:
php artisan make:factory TaskFactory
<?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,
];
}
}
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>
Create an integration test:
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]);
}
}
Run the tests:
php artisan test
*Thank you! *