Tutorial Membuat REST API dengan Laravel yang Mudah dan Praktis

Tutorial
Apr 26, 2026
17 mnt baca
77 tayangan
Tutorial Membuat REST API dengan Laravel yang Mudah dan Praktis
A

Admin

Penulis Artikel

Tutorial Lengkap Laravel REST API

Best Practice — Studi Kasus Todo List

BACA JUGA: Panduan Lengkap Menginstall SASS di Windows


1. Pendahuluan

Di era modern pengembangan perangkat lunak, arsitektur berbasis API (Application Programming Interface) menjadi standar yang hampir wajib dikuasai oleh setiap developer. Mulai dari aplikasi mobile, Single Page Application (SPA), hingga integrasi antar layanan — semuanya membutuhkan API yang handal, aman, dan mudah di-maintain.

Tutorial ini cocok untuk kamu yang:

  • Baru mulai belajar Laravel dan ingin langsung belajar membuat REST API

  • Sudah tahu dasar Laravel MVC tapi ingin memahami struktur API yang baik

  • Ingin memiliki boilerplate proyek REST API yang siap dikembangkan lebih lanjut

Di tutorial ini kita akan membangun:

  • Autentikasi menggunakan JWT (JSON Web Token)

  • Sistem role sederhana: admin dan user

  • CRUD Todo — admin bisa kelola semua, user hanya miliknya sendiri

  • Controller di folder API/, menggunakan API Resource dan Form Request


2. Mengapa Laravel 11?

📖 Referensi: laravel.com/docs/11.x/releases

Laravel 11 dirilis pada Maret 2024 dan membawa banyak perubahan signifikan dari versi sebelumnya. Berikut alasan mengapa tutorial ini menggunakan Laravel 11:

Alasan Teknis

a. Struktur Lebih Ramping (Slim Skeleton)

Laravel 11 memangkas banyak file yang sebelumnya ada di versi 10 ke bawah. File app/Http/Kernel.php dihapus dan digantikan dengan konfigurasi middleware langsung di bootstrap/app.php. Ini membuat proyek lebih bersih dan tidak bloated untuk pemula.

b. Route API Dipisah Sejak Awal

Di Laravel 11, file routes/api.php tidak lagi di-load secara otomatis. Kamu harus menginstallnya dulu dengan php artisan install:api, yang mengajarkan pemula bahwa pemisahan route web dan API adalah praktik yang disengaja.

c. Support PHP 8.2+

Laravel 11 mengharuskan PHP 8.2 ke atas, artinya kamu otomatis menggunakan fitur PHP modern seperti readonly properties, enum, dan fibers.

d. LTS Terkini

Laravel 11 mendapat dukungan bug fix hingga Agustus 2025 dan security fix hingga Agustus 2026, menjadikannya pilihan stabil untuk proyek jangka panjang.

e. Jembatan Migrasi yang Mulus

Laravel dirancang dengan prinsip keberlanjutan. Memulai dengan Laravel 11 adalah langkah strategis karena framework ini berbagi inti mesin yang sama dengan versi terbaru. Perubahan yang ada bersifat evolusioner, bukan revolusioner, sehingga ketika Anda memutuskan untuk melakukan pembaruan ke versi di atasnya, proses transisi dapat dilakukan dengan sangat minim perubahan kode (seamless upgrade path). Ini menjadikan Laravel 11 sebagai titik mulai yang aman sekaligus relevan untuk jangka panjang.


3. Apa itu REST API?

REST (Representational State Transfer) adalah sebuah architectural style untuk membangun layanan web. API yang mengikuti prinsip REST disebut RESTful API. REST bekerja di atas protokol HTTP dan menggunakan URL (endpoint) untuk mengidentifikasi resource, serta HTTP Method untuk mendefinisikan aksi terhadap resource tersebut.

HTTP Methods yang Wajib Kamu Tahu

Method

Kegunaan

Contoh Endpoint

Aksi

GET

Mengambil daftar data

GET /api/todos

Ambil semua todo

GET

Mengambil satu data

GET /api/todos/1

Ambil todo ID 1

POST

Membuat data baru

POST /api/todos

Buat todo baru

PUT

Update semua field

PUT /api/todos/1

Update seluruh data todo ID 1

PATCH

Update sebagian field

PATCH /api/todos/1

Update sebagian data todo ID 1

DELETE

Menghapus data

DELETE /api/todos/1

Hapus todo ID 1

HTTP Status Code yang Umum Digunakan

Kode

Nama

Kapan Digunakan

200

OK

Request berhasil (GET, PUT, PATCH)

201

Created

Data berhasil dibuat (POST)

204

No Content

Berhasil tapi tidak ada response body (DELETE)

400

Bad Request

Request tidak valid

401

Unauthorized

Belum login / token tidak valid

403

Forbidden

Sudah login tapi tidak punya izin

404

Not Found

Data tidak ditemukan

422

Unprocessable Entity

Validasi gagal

500

Internal Server Error

Error di server

Contoh Format Response JSON yang Konsisten

Response API yang baik selalu menggunakan format yang konsisten. Berikut format yang akan kita gunakan:

Response Sukses:

{
  "success": true,
  "message": "Todos retrieved successfully",
  "data": [
    {
      "id": 1,
      "title": "Belajar Laravel",
      "is_completed": false,
      "user": { "id": 1, "name": "Budi" }
    }
  ]
}

Response Error:

{
  "success": false,
  "message": "Validation failed",
  "errors": {
    "title": ["The title field is required."] }
}

4. Mengapa Laravel Cocok untuk REST API?

Laravel memiliki fitur-fitur bawaan yang sangat membantu dalam membangun REST API yang profesional:

  • Eloquent ORM — Interaksi dengan database menjadi sangat mudah dan ekspresif

  • API Resource — Mengontrol secara presisi data yang dikembalikan ke client (docs)

  • Form Request — Memindahkan logika validasi dari controller ke kelas tersendiri

  • Middleware — Mudah menambahkan lapisan keamanan seperti autentikasi dan otorisasi role

  • Route Groups & Prefix — Mudah mengelompokkan route API dengan prefix /api/v1/

  • JWT / Sanctum — Dukungan autentikasi API yang fleksibel


5. Persiapan & Instalasi

📖 Referensi: laravel.com/docs/11.x/installation

Persyaratan Sistem

Kebutuhan

Versi Minimum

PHP

8.2

Composer

2.x

MySQL

8.0 / MariaDB 10.x

Node.js

18.x (opsional, untuk frontend)

Postman

Versi terbaru

Instalasi Laravel 11

Buka terminal dan jalankan salah satu perintah berikut:

composer create-project laravel/laravel:^11.0 laravel-todo

Masuk ke folder proyek dan jalankan server:

cd laravel-todo 
php artisan serve

Buka browser dan kunjungi http://127.0.0.1:8000. Jika muncul halaman welcome Laravel, instalasi berhasil.

Instalasi Route API

Di Laravel 11, route API tidak otomatis tersedia. Kita perlu menginstallnya:

php artisan install:api

Perintah ini akan membuat file routes/api.php, migration untuk tabel personal_access_tokens, dan mendaftarkan route API secara otomatis.


6. Struktur Proyek

Berikut gambaran struktur folder yang akan kita bangun:

laravel-todo/
├── app/
│   ├── Http/
│   │   ├── Controllers/
│   │   │   └── API/                ← Controller khusus API
│   │   │       ├── AuthController.php
│   │   │       ├── TodoController.php
│   │   │       └── UserController.php
│   │   ├── Middleware/
│   │   │   └── RoleMiddleware.php   ← Middleware role
│   │   └── Requests/
│   │       ├── Auth/
│   │       │   ├── LoginRequest.php
│   │       │   └── RegisterRequest.php
│   │       └── Todo/
│   │           ├── StoreTodoRequest.php
│   │           └── UpdateTodoRequest.php
│   ├── Models/
│   │   ├── User.php
│   │   └── Todo.php
│   └── Http/
│       └── Resources/
│           ├── TodoResource.php     ← API Resource
│           └── UserResource.php
├── database/
│   ├── migrations/
│   └── seeders/
├── routes/
│   ├── api.php                      ← Route API
│   └── web.php                      ← Route Web (untuk MVC nanti)
└── bootstrap/
    └── app.php                      ← Konfigurasi middleware (baru di L11)

💡 Mengapa controller diletakkan di folder API/?

  • Agar controller untuk API terpisah dari controller untuk tampilan web (MVC)

  • Jika suatu saat menambahkan Blade, tidak perlu mencampur logika API dan Web

  • Memudahkan maintenance dan pengembangan tim besar

  • Merupakan best practice yang direkomendasikan industri


Konfigurasi Database

Buat Database

CREATE DATABASE laravel_todo;

Konfigurasi .env

Buka file .env di root proyek dan sesuaikan:

APP_NAME="Laravel Todo API"
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost

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

JWT_SECRET=        # akan diisi setelah install JWT
JWT_ALGO=HS256
JWT_TTL=60         # token expired dalam 60 menit

8. Membuat Migration & Model

📖 Referensi: laravel.com/docs/11.x/eloquent  |  laravel.com/docs/11.x/migrations

Migration: Tambah Kolom Role ke Tabel Users

php artisan make:migration add_role_to_users_table --table=users
<?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::table('users', function (Blueprint $table) {
            $table->enum('role', ['admin', 'user'])->default('user')->after('email');
        });
    }
    public function down(): void {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('role');
        });
    }
};

Migration: Tabel Todos

php artisan make:migration create_todos_table
<?php
return new class extends Migration {
    public function up(): void {
        Schema::create('todos', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->onDelete('cascade');
            $table->string('title');
            $table->text('description')->nullable();
            $table->boolean('is_completed')->default(false);
            $table->timestamps();
        });
    }
    public function down(): void {
        Schema::dropIfExists('todos');
    }
};
php artisan migrate

Model User (app/Models/User.php)

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use PHPOpenSourceSaver\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use HasFactory, Notifiable;
    protected $fillable = ['name', 'email', 'password', 'role'];
    protected $hidden   = ['password', 'remember_token'];

    protected function casts(): array {
        return ['email_verified_at' => 'datetime', 'password' => 'hashed'];
    }

    // Relasi: satu user punya banyak todo
    public function todos() {
        return $this->hasMany(Todo::class);
    }

    // Helper: cek apakah user adalah admin
    public function isAdmin(): bool {
        return $this->role === 'admin';
    }

    // Wajib untuk JWT
    public function getJWTIdentifier()    { return $this->getKey(); }
    public function getJWTCustomClaims() { return []; }
}

Model Todo (app/Models/Todo.php)

php artisan make:model Todo
<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
    protected $fillable = ['user_id', 'title', 'description', 'is_completed'];
    protected $casts    = ['is_completed' => 'boolean'];

    // Relasi: todo dimiliki oleh seorang user
    public function user() {
        return $this->belongsTo(User::class);
    }
}

9. Instalasi & Konfigurasi JWT

JWT, atau JSON Web Token, adalah standar terbuka yang digunakan untuk pertukaran data yang aman antara dua pihak. Dalam konteks REST API di Laravel, JWT sangat berguna untuk mengotentikasi pengguna. Dengan menggunakan JWT, server dapat mengeluarkan token kepada pengguna setelah mereka berhasil melakukan login. Token ini kemudian digunakan dalam setiap permintaan berikutnya untuk mengakses sumber daya yang dilindungi.

Package php-open-source-saver/jwt-auth adalah salah satu solusi yang populer dan kompatibel dengan Laravel 11. Package ini memungkinkan pengembang untuk dengan mudah mengimplementasikan otentikasi berbasis token di aplikasi Laravel Dengan menggunakan JWT, pengembang dapat memastikan bahwa hanya pengguna yang telah terautentikasi yang dapat mengakses API, sehingga meningkatkan keamanan aplikasi secara keseluruhan. Selain itu, JWT bersifat stateless, yang berarti server tidak perlu menyimpan informasi sesi, menjadikannya lebih efisien dalam penggunaan sumber daya.

Install Package

composer require php-open-source-saver/jwt-auth

Publish Config

php artisan vendor:publish --provider="PHPOpenSourceSaver\JWTAuth\Providers\LaravelServiceProvider"

Generate JWT Secret Key

php artisan jwt:secret

Perintah ini akan otomatis mengisi JWT_SECRET di file .env kamu.

Konfigurasi Auth Guard (config/auth.php)

 'defaults' => [
        'guard' => env('AUTH_GUARD', 'web'),
        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
    ],

/* kode lainnya */

 'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [
            'driver'   => 'jwt',   // ← ubah dari 'token' ke 'jwt'
            'provider' => 'users',
        ],
        
    ],

10. Membuat Seeder & Roles

php artisan make:seeder UserSeeder
<?php
namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;

class UserSeeder extends Seeder
{
    public function run(): void {
        // Buat akun Admin
        User::create([
            'name'     => 'Admin',
            'email'    => 'admin@example.com',
            'password' => Hash::make('password'),
            'role'     => 'admin',
        ]);

        // Buat akun User biasa
        User::create([
            'name'     => 'User Biasa',
            'email'    => 'user@example.com',
            'password' => Hash::make('password'),
            'role'     => 'user',
        ]);
    }
}

Pada DatabaseSeeder.php

<?php

namespace Database\Seeders;

use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $this->call([
        UserSeeder::class,
        // Tambahkan seeder lain di sini jika ada
    ]);
    }
}

Setelah didaftarkan di DatabaseSeeder.php lalu jalankan:

php artisan db:seed

11. Membuat API Resource

📖 Referensi: laravel.com/docs/11.x/eloquent-resources

API Resource memungkinkan kita mengontrol secara presisi bentuk data JSON yang dikirim ke client. Ini adalah best practice agar model database tidak langsung "bocor" ke respons API.

⚠️ Mengapa harus pakai API Resource?

  • Model User menyimpan kolom password. Tanpa Resource, return User::all() akan mengirim data sensitif ke client!

  • Kamu mengontrol sepenuhnya field apa yang tampil di response

  • Bisa menambahkan computed fields yang tidak ada di tabel database

TodoResource

php artisan make:resource TodoResource
<?php
namespace App\Http\Resources;

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

class TodoResource extends JsonResource
{
    public function toArray(Request $request): array {
        return [
            'id'           => $this->id,
            'title'        => $this->title,
            'description'  => $this->description,
            'is_completed' => $this->is_completed,
            'created_at'   => $this->created_at->toDateTimeString(),
            'updated_at'   => $this->updated_at->toDateTimeString(),
            // Hanya tampil jika relasi user di-load dengan ->load('user')
            'user'         => new UserResource($this->whenLoaded('user')),
        ];
    }
}

UserResource

php artisan make:resource UserResource
<?php
namespace App\Http\Resources;

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

class UserResource extends JsonResource
{
     public function toArray(Request $request): array {
        return [
            'id'    => $this->id,
            'name'  => $this->name,
            'email' => $this->email,
            'role'  => $this->role,
            // TIDAK menampilkan password!
        ];
    }
}

12. Membuat Form Request

📖 Referensi: laravel.com/docs/11.x/validation#form-request-validation

Form Request adalah kelas khusus untuk validasi dan otorisasi sebelum data masuk ke controller. Controller menjadi lebih bersih (Single Responsibility Principle).

RegisterRequest

php artisan make:request Auth/RegisterRequest
<?php
namespace App\Http\Requests\Auth;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Contracts\Validation\Validator;

class RegisterRequest extends FormRequest
{
    public function authorize(): bool { return true; }

    public function rules(): array {
        return [
            'name'     => 'required|string|max:255',
            'email'    => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:8|confirmed',
        ];
    }

    public function messages(): array {
        return [
            'name.required'      => 'Nama wajib diisi.',
            'email.required'     => 'Email wajib diisi.',
            'email.unique'       => 'Email sudah terdaftar.',
            'password.confirmed' => 'Konfirmasi password tidak cocok.',
        ];
    }

    // Override agar error validasi dikembalikan sebagai JSON
    protected function failedValidation(Validator $validator) {
        throw new HttpResponseException(response()->json([
            'success' => false,
            'message' => 'Validation failed',
            'errors'  => $validator->errors(),
        ], 422));
    }
}

LoginRequest

php artisan make:request Auth/LoginRequest
<?php

namespace App\Http\Requests\Auth;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Contracts\Validation\Validator;
class LoginRequest extends FormRequest
{
    public function authorize(): bool { return true; }

    public function rules(): array {
        return [
            'email'    => 'required|string|email',
            'password' => 'required|string',
        ];
    }

    protected function failedValidation(Validator $validator) {
        throw new HttpResponseException(response()->json([
            'success' => false, 'message' => 'Validation failed',
            'errors'  => $validator->errors(),
        ], 422));
    }
}

StoreTodoRequest

php artisan make:request Todo/StoreTodoRequest
<?php

namespace App\Http\Requests\Todo;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Contracts\Validation\Validator;
class StoreTodoRequest extends FormRequest
{
    public function authorize(): bool { return true; }

    public function rules(): array {
        return [
            'title'        => 'required|string|max:255',
            'description'  => 'nullable|string',
            'is_completed' => 'boolean',
        ];
    }

    protected function failedValidation(Validator $validator) {
        throw new HttpResponseException(response()->json([
            'success' => false, 'message' => 'Validation failed',
            'errors'  => $validator->errors(),
        ], 422));
    }
}

UpdateTodoRequest

php artisan make:request Todo/UpdateTodoRequest
<?php

namespace App\Http\Requests\Todo;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Contracts\Validation\Validator;
class UpdateTodoRequest extends FormRequest
{
    public function authorize(): bool { return true; }

    public function rules(): array {
        return [
            'title'        => 'sometimes|required|string|max:255',
            'description'  => 'nullable|string',
            'is_completed' => 'boolean',
        ];
    }

    protected function failedValidation(Validator $validator) {
        throw new HttpResponseException(response()->json([
            'success' => false, 'message' => 'Validation failed',
            'errors'  => $validator->errors(),
        ], 422));
    }
}

13. Membuat Controller di Folder API

AuthController

php artisan make:controller API/AuthController
<?php
namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Http\Requests\Auth\{LoginRequest, RegisterRequest};
use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    /** POST /api/auth/register */
    public function register(RegisterRequest $request): JsonResponse {
        $user  = User::create([...$request->validated(), 'role' => 'user']);
        $token = Auth::login($user);

        return response()->json([
            'success' => true,
            'message' => 'User registered successfully',
            'data'    => ['user' => new UserResource($user), 'token' => $token, 'type' => 'bearer'],
        ], 201);
    }

    /** POST /api/auth/login */
    public function login(LoginRequest $request): JsonResponse {
        if (!$token = Auth::attempt($request->only('email', 'password'))) {
            return response()->json(['success' => false, 'message' => 'Email atau password salah'], 401);
        }
        return response()->json([
            'success' => true,
            'message' => 'Login successful',
            'data'    => [
                'user'       => new UserResource(Auth::user()),
                'token'      => $token,
                'type'       => 'bearer',
                'expires_in' => config('jwt.ttl') * 60,
            ],
        ]);
    }

    /** POST /api/auth/logout */
    public function logout(): JsonResponse {
        Auth::logout();
        return response()->json(['success' => true, 'message' => 'Successfully logged out']);
    }

    /** POST /api/auth/refresh */
    public function refresh(): JsonResponse {
        return response()->json([
            'success' => true,
            'data'    => ['user' => new UserResource(Auth::user()), 'token' => Auth::refresh()],
        ]);
    }

    /** GET /api/auth/me */
    public function me(): JsonResponse {
        return response()->json(['success' => true, 'data' => new UserResource(Auth::user())]);
    }
}

TodoController

php artisan make:controller API/TodoController
<?php
namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Http\Requests\Todo\{StoreTodoRequest, UpdateTodoRequest};
use App\Http\Resources\TodoResource;
use App\Models\Todo;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;

class TodoController extends Controller
{
    /** GET /api/todos
     * - Admin  : lihat semua todo
     * - User   : hanya lihat miliknya sendiri
     */
    public function index(): JsonResponse {
        $user  = Auth::user();
        $todos = $user->isAdmin()
            ? Todo::with('user')->latest()->paginate(10)
            : Todo::where('user_id', $user->id)->latest()->paginate(10);

        return response()->json([
            'success' => true,
            'message' => 'Todos retrieved successfully',
            'data'    => TodoResource::collection($todos),
            'meta'    => [
                'current_page' => $todos->currentPage(),
                'last_page'    => $todos->lastPage(),
                'per_page'     => $todos->perPage(),
                'total'        => $todos->total(),
            ],
        ]);
    }

    /** POST /api/todos */
    public function store(StoreTodoRequest $request): JsonResponse {
        $todo = Todo::create(['user_id' => Auth::id(), ...$request->validated()]);
        return response()->json([
            'success' => true,
            'message' => 'Todo created successfully',
            'data'    => new TodoResource($todo),
        ], 201);
    }

    /** GET /api/todos/{id} */
    public function show(Todo $todo): JsonResponse {
        if (!Auth::user()->isAdmin() && $todo->user_id !== Auth::id()) {
            return response()->json(['success' => false, 'message' => 'Unauthorized.'], 403);
        }
        return response()->json(['success' => true, 'data' => new TodoResource($todo->load('user'))]);
    }

    /** PUT/PATCH /api/todos/{id} */
    public function update(UpdateTodoRequest $request, Todo $todo): JsonResponse {
        if (!Auth::user()->isAdmin() && $todo->user_id !== Auth::id()) {
            return response()->json(['success' => false, 'message' => 'Unauthorized.'], 403);
        }
        $todo->update($request->validated());
        return response()->json(['success' => true, 'message' => 'Todo updated successfully', 'data' => new TodoResource($todo)]);
    }

    /** DELETE /api/todos/{id} */
    public function destroy(Todo $todo): JsonResponse {
        if (!Auth::user()->isAdmin() && $todo->user_id !== Auth::id()) {
            return response()->json(['success' => false, 'message' => 'Unauthorized.'], 403);
        }
        $todo->delete();
        return response()->json(['success' => true, 'message' => 'Todo deleted successfully']);
    }
}

UserController (Khusus Admin)

php artisan make:controller API/UserController
<?php
namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\JsonResponse;

class UserController extends Controller
{
    /** GET /api/users — daftar semua user (khusus admin) */
    public function index(): JsonResponse {
        $users = User::withCount('todos')->paginate(10);
        return response()->json(['success' => true, 'data' => UserResource::collection($users)]);
    }

    /** GET /api/users/{id} — detail user (khusus admin) */
    public function show(User $user): JsonResponse {
        return response()->json(['success' => true, 'data' => new UserResource($user->load('todos'))]);
    }
}

14. Mendefinisikan Routes API

📖 Referensi: laravel.com/docs/11.x/routing

Buka routes/api.php dan definisikan semua route:

<?php

use App\Http\Controllers\API\{AuthController, TodoController, UserController};
use Illuminate\Support\Facades\Route;

// ─── Public Routes (Tidak Perlu Token) ───────────────────────────────────
Route::prefix('auth')->group(function () {
    Route::post('/register', [AuthController::class, 'register']);
    Route::post('/login',    [AuthController::class, 'login']);
});

// ─── Protected Routes (Butuh JWT Token) ──────────────────────────────────
Route::middleware('auth:api')->group(function () {

    // Auth Routes
    Route::prefix('auth')->group(function () {
        Route::post('/logout',  [AuthController::class, 'logout']);
        Route::post('/refresh', [AuthController::class, 'refresh']);
        Route::get('/me',       [AuthController::class, 'me']);
    });

    // Todo Routes (semua user, logika admin/user di controller)
    Route::apiResource('todos', TodoController::class);

    // User Routes (khusus admin)
    Route::middleware('role:admin')->group(function () {
        Route::apiResource('users', UserController::class)->only(['index', 'show']);
    });
});

Route yang Dihasilkan oleh apiResource

Method

URI

Action

Route Name

GET

/api/todos

index

todos.index

POST

/api/todos

store

todos.store

GET

/api/todos/{id}

show

todos.show

PUT/PATCH

/api/todos/{id}

update

todos.update

DELETE

/api/todos/{id}

destroy

todos.destroy

Cek semua route yang terdaftar:

php artisan route:list --path=api

15. Middleware Role

📖 Referensi: laravel.com/docs/11.x/middleware

Buat RoleMiddleware

php artisan make:middleware RoleMiddleware
<?php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class RoleMiddleware
{
    public function handle(Request $request, Closure $next, string ...$roles) {
        $user = Auth::user();

        if (!$user) {
            return response()->json(['success' => false, 'message' => 'Unauthenticated.'], 401);
        }

        if (!in_array($user->role, $roles)) {
            return response()->json([
                'success'        => false,
                'message'        => 'Forbidden. You do not have the required role.',
                'required_roles' => $roles,
                'your_role'      => $user->role,
            ], 403);
        }

        return $next($request);
    }
}

Daftarkan di bootstrap/app.php

Di Laravel 11, middleware didaftarkan di bootstrap/app.phpbukan di Kernel.php seperti versi lama:

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        // Daftarkan alias untuk middleware role
        $middleware->alias([
            'role' => \App\Http\Middleware\RoleMiddleware::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        // Biarkan kosong jika tidak ada kustomisasi
    })
    ->create();

16. Testing di Postman

Pastikan server Laravel berjalan: php artisan serve

Kemudian buka Postman dan ikuti langkah-langkah berikut.

Setup Environment di Postman

Variable

Value

Keterangan

base_url

http://127.0.0.1:8000/api

Base URL API

token

(kosong dulu)

Akan diisi otomatis setelah login

📸 Screenshot: Postman — Setup Environment

enviroment postman

1. Register User Baru

Field

Value

Method

POST

URL

{{base_url}}/auth/register

Header

Content-Type: application/json   Accept: application/json

Body (raw JSON):

{
  "name": "Ainun",
  "email": "ainun@example.com",
  "password": "password123",
  "password_confirmation": "password123"
}

Response yang diharapkan (201 Created):

{
  "success": true,
  "message": "User registered successfully",
  "data": {
    "user": { "id": 3, "name": "Ainun", "email": "ainun@example.com", "role": "user" },
    "token": "eyJ0eXAiOiJKV1Qi...",
    "type": "bearer"
  }
}

📸 Screenshot: Postman — Register Request & Response 201 Created

postman register user

2. Login

{
  "email": "admin@example.com",
  "password": "password"
}

Tips: Tambahkan Script di tab Scripts Postman untuk menyimpan token otomatis:

// Tab "Scripts" di Postman
var response = pm.response.json();
if (response.data && response.data.token) {
    pm.environment.set("token", response.data.token);
    console.log("Token saved:", response.data.token);
}

📸 Screenshot: Postman — Login dengan auto-save token di tab Tests

postman login scripts

postman login

3. Menggunakan Token (Authorization)

Untuk semua request yang membutuhkan autentikasi, tambahkan header:

Authorization: Bearer {{token}}

Atau gunakan tab Authorization → Type: Bearer Token → Value: {{token}}

📸 Screenshot: Postman — Tab Authorization dengan Bearer Token aktif

postman auth token

4. Buat Todo Baru

Method: POST   URL: {{base_url}}/todos   (sertakan Bearer Token)

{
  "title": "Belajar Laravel 11",
  "description": "Pelajari fitur baru Laravel 11 termasuk struktur slim skeleton",
  "is_completed": false
}

📸 Screenshot: Postman — Create Todo POST Request & Response 201

postman add todos

5. Ambil Semua Todo

Method: GET   URL: {{base_url}}/todos   (sertakan Bearer Token)

postman get todos

6. Update Todo

Method: PUT   URL: {{base_url}}/todos/1

{
  "title": "Belajar Laravel 11 - Updated",
  "is_completed": true
}

📸 Screenshot: Postman — Update Todo POST Request & Response 200

postman update todos

7. Hapus Todo

Method: DELETE   URL: {{base_url}}/todos/1

postman delete todos

8. Test Role — Akses Daftar User (Khusus Admin)

Login sebagai user lalu akses GET {{base_url}}/users. Jika menggunakan token user biasa, response akan:

{
  "success": false,
  "message": "Forbidden. You do not have the required role.",
  "required_roles": ["admin"],
  "your_role": "user"
}

📸 Screenshot: Postman — Test Role Forbidden 403 Response

postman login user

postman forbiden role

Rangkuman Semua Endpoint

Method

Endpoint

Auth

Role

Keterangan

POST

/api/auth/register

Tidak

-

Daftar akun baru

POST

/api/auth/login

Tidak

-

Login

POST

/api/auth/logout

Ya

-

Logout

POST

/api/auth/refresh

Ya

-

Refresh token

GET

/api/auth/me

Ya

-

Data user login

GET

/api/todos

Ya

admin/user

Lihat todo

POST

/api/todos

Ya

admin/user

Buat todo

GET

/api/todos/{id}

Ya

admin/user

Detail todo

PUT/PATCH

/api/todos/{id}

Ya

admin/user

Update todo

DELETE

/api/todos/{id}

Ya

admin/user

Hapus todo

GET

/api/users

Ya

admin

Daftar semua user

GET

/api/users/{id}

Ya

admin

Detail user


17. Persiapan untuk Fitur MVC di Masa Depan

Salah satu keuntungan besar dari struktur yang sudah kita bangun adalah kemudahannya untuk dikembangkan dengan tampilan web berbasis Blade (MVC) di masa depan.

Yang TIDAK Perlu Diubah

  • Model (app/Models/) — digunakan bersama API dan Web

  • Migration & Database — sama persis

  • Seeder — sama persis

  • Logika bisnis ([opsional] bisa di-refactor ke Service layer)

Yang Perlu Ditambahkan Nanti

1. Web Controller (terpisah dari API Controller)

php artisan make:controller Web/TodoController

2. Route Web di routes/web.php

Route::middleware(['auth'])->group(function () {
    Route::resource('todos', \App\Http\Controllers\Web\TodoController::class);
});

3. Instalasi Laravel Breeze untuk tampilan web

composer require laravel/breeze --dev php artisan breeze:install blade

Tips: Service Layer untuk Kode yang Lebih Bersih

Agar tidak ada duplikasi logika antara API dan Web Controller, pertimbangkan membuat Service Layer:

// app/Services/TodoService.php
class TodoService
{
    public function getAllTodosForUser(User $user) {
        return $user->isAdmin()
            ? Todo::with('user')->latest()->paginate(10)
            : Todo::where('user_id', $user->id)->latest()->paginate(10);
    }

    public function createTodo(array $data): Todo {
        return Todo::create($data);
    }
}

// Baik API/TodoController maupun Web/TodoController
// sama-sama memanggil TodoService — tidak ada duplikasi logika!

✅ Gambaran Arsitektur dengan Service Layer:

  • API/TodoController → memanggil TodoService → return JSON

  • Web/TodoController → memanggil TodoService → return Blade View

  • TodoService → berinteraksi dengan Model Todo

  • Logika bisnis hanya ada di satu tempat — mudah di-maintain!


18. Kesimpulan & Referensi

Apa yang Telah Kita Bangun?

  • Instalasi Laravel 11 yang benar dengan struktur slim skeleton

  • Autentikasi JWT yang aman menggunakan php-open-source-saver/jwt-auth

  • Sistem Role (admin & user) dengan Middleware custom

  • API Resource untuk mengontrol format respons JSON

  • Form Request untuk validasi yang terpisah dari controller

  • Controller terstruktur di folder API/ yang siap untuk pengembangan MVC

  • Testing lengkap di Postman dengan auto-save token

Langkah Selanjutnya

  1. Menambahkan fitur kategori todo (relasi many-to-many)

  2. Menambahkan filter, sorting, dan search di endpoint GET /todos

  3. Membuat unit test dengan PHPUnit atau Pest

  4. Deploy ke production menggunakan Nginx + PHP-FPM

  5. Menambahkan rate limiting untuk keamanan API

Referensi Resmi Laravel 11

Tutorial Laravel 11 REST API Best Practice — Studi Kasus Todo List dengan JWT & Role

Referensi resmi: laravel.com/docs/11.x

Artikel Terkait

Best Practices Pengembangan Proyek Python dengan .env dan Dotenv

Pelajari cara mengelola konfigurasi proyek Python Anda dengan .env dan dotenv untuk pengembangan yang lebih efisien.

Baca Artikel
Tips dan Trik Menggunakan SASS untuk Desain Web yang Lebih Baik

Temukan tips dan trik penggunaan SASS untuk meningkatkan efisiensi desain web Anda dengan mudah.

Baca Artikel
Panduan Membuat Animasi Animated on Scroll di Website Anda

Pelajari cara membuat animasi menarik pada scroll di website Anda dengan tutorial ini. Tampilkan konten dengan cara yang lebih interaktif!

Baca Artikel