Building a REST API with Laravel 12: Complete Guide for Beginners

calendar_today July 6, 2026
person info@softcrony.com
folder DevOps

Laravel makes building REST APIs surprisingly straightforward. In this guide we’ll build a complete, production-ready API from scratch using Laravel 12 — covering everything from setup to authentication.

By the end you’ll have a fully working API that follows REST best practices and is ready to connect to any frontend — React, Vue, mobile app, or third-party service.

What We’re Building

  • Full CRUD operations (Create, Read, Update, Delete)
  • API authentication using Laravel Sanctum
  • API Resources for consistent response formatting
  • Request validation
  • Proper HTTP status codes and error handling

Prerequisites

  • PHP 8.2+
  • Composer installed
  • Basic understanding of PHP and MVC concepts
  • Postman or Hoppscotch for testing

Step 1 — Create a New Laravel 12 Project

composer create-project laravel/laravel taskapi
cd taskapi

Set up your database in .env:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=taskapi
DB_USERNAME=root
DB_PASSWORD=

Step 2 — Create the Task Model and Migration

php artisan make:model Task -m

Open the migration file and define the schema:

<?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->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->string('title');
            $table->text('description')->nullable();
            $table->enum('status', ['pending', 'in_progress', 'completed'])->default('pending');
            $table->timestamp('due_date')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('tasks');
    }
};
php artisan migrate

Step 3 — Set Up the Task Model

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Task extends Model
{
    protected $fillable = [
        'title',
        'description',
        'status',
        'due_date',
        'user_id',
    ];

    protected $casts = [
        'due_date' => 'datetime',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}

Step 4 — Install Laravel Sanctum

Laravel 12 includes Sanctum by default. Run:

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Add HasApiTokens to your User model:

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;
}

Step 5 — Create an API Resource

php artisan make:resource TaskResource
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class TaskResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'          => $this->id,
            'title'       => $this->title,
            'description' => $this->description,
            'status'      => $this->status,
            'due_date'    => $this->due_date?->toDateString(),
            'created_at'  => $this->created_at->toDateTimeString(),
        ];
    }
}

Step 6 — Create the Task Controller

php artisan make:controller Api/TaskController --api
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Resources\TaskResource;
use App\Models\Task;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class TaskController extends Controller
{
    public function index(Request $request)
    {
        $tasks = $request->user()->tasks()->latest()->paginate(10);
        return TaskResource::collection($tasks);
    }

    public function store(Request $request): TaskResource
    {
        $validated = $request->validate([
            'title'       => 'required|string|max:255',
            'description' => 'nullable|string',
            'status'      => 'in:pending,in_progress,completed',
            'due_date'    => 'nullable|date',
        ]);

        $task = $request->user()->tasks()->create($validated);
        return new TaskResource($task);
    }

    public function show(Request $request, Task $task): TaskResource
    {
        $this->authorize('view', $task);
        return new TaskResource($task);
    }

    public function update(Request $request, Task $task): TaskResource
    {
        $this->authorize('update', $task);

        $validated = $request->validate([
            'title'       => 'sometimes|string|max:255',
            'description' => 'nullable|string',
            'status'      => 'in:pending,in_progress,completed',
            'due_date'    => 'nullable|date',
        ]);

        $task->update($validated);
        return new TaskResource($task);
    }

    public function destroy(Request $request, Task $task): JsonResponse
    {
        $this->authorize('delete', $task);
        $task->delete();
        return response()->json(['message' => 'Task deleted successfully']);
    }
}

Step 7 — Create Auth Controller

php artisan make:controller Api/AuthController
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    public function register(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'name'     => 'required|string|max:255',
            'email'    => 'required|email|unique:users',
            'password' => 'required|min:8|confirmed',
        ]);

        $user = User::create([
            'name'     => $validated['name'],
            'email'    => $validated['email'],
            'password' => Hash::make($validated['password']),
        ]);

        $token = $user->createToken('api-token')->plainTextToken;

        return response()->json(['user' => $user, 'token' => $token], 201);
    }

    public function login(Request $request): JsonResponse
    {
        $request->validate([
            'email'    => 'required|email',
            'password' => 'required',
        ]);

        $user = User::where('email', $request->email)->first();

        if (!$user || !Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['The provided credentials are incorrect.'],
            ]);
        }

        $token = $user->createToken('api-token')->plainTextToken;
        return response()->json(['user' => $user, 'token' => $token]);
    }

    public function logout(Request $request): JsonResponse
    {
        $request->user()->currentAccessToken()->delete();
        return response()->json(['message' => 'Logged out successfully']);
    }
}

Step 8 — Define API Routes

<?php

use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\TaskController;
use Illuminate\Support\Facades\Route;

Route::post('/register', [AuthController::class, 'register']);
Route::post('/login',    [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::apiResource('tasks', TaskController::class);
});

Step 9 — Test the API

php artisan serve

Register a user:

POST http://localhost:8000/api/register
Content-Type: application/json

{
  "name": "Atul",
  "email": "atul@example.com",
  "password": "password123",
  "password_confirmation": "password123"
}

Create a task:

POST http://localhost:8000/api/tasks
Authorization: Bearer YOUR_TOKEN_HERE
Content-Type: application/json

{
  "title": "Build the API",
  "description": "Complete the Laravel REST API guide",
  "status": "in_progress",
  "due_date": "2026-06-30"
}

API Endpoints Summary

Method Endpoint Description Auth
POST /api/register Register new user No
POST /api/login Login user No
POST /api/logout Logout user Yes
GET /api/tasks List all tasks Yes
POST /api/tasks Create task Yes
GET /api/tasks/{id} Get single task Yes
PUT /api/tasks/{id} Update task Yes
DELETE /api/tasks/{id} Delete task Yes

Best Practices for Production

Version your API: Use /api/v1/ prefix so you can release v2 without breaking existing clients.

Rate limiting:

Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
    // your routes
});

Cache repeated responses:

$tasks = cache()->remember('user_tasks_' . $request->user()->id, 300, function () use ($request) {
    return $request->user()->tasks()->latest()->paginate(10);
});

If you need a custom REST API built for your business — whether it’s a mobile app backend, third-party integration, or internal tool — our team at Softcrony is happy to help.

Leave a comment