Langsung ke konten
KamusNgoding
Mahir Laravel 4 menit baca

Membuat REST API dengan Laravel Sanctum

#laravel #api #sanctum #rest-api #token #json #postman #advanced

Pendahuluan

Kita sudah memiliki Task Manager yang berjalan dengan baik sebagai aplikasi web. Tapi bagaimana jika tim mobile ingin membuat aplikasi Android/iOS yang menggunakan data yang sama? Atau bagaimana jika frontend React/Vue ingin berkomunikasi dengan backend Laravel?

Jawabannya adalah REST API.

Laravel Sanctum menyediakan sistem autentikasi yang ringan untuk:

  • API Token (untuk aplikasi mobile dan pihak ketiga)
  • SPA Authentication (untuk aplikasi React/Vue yang one-domain)

Di artikel ini kita akan mengubah Task Manager menjadi API yang bisa dikonsumsi oleh siapapun dengan token yang valid.


REST API: Konsep Dasar

Sebelum coding, pastikan kamu memahami konsep ini:

HTTP Methods & Maknanya

MethodAksiContoh
GETAmbil dataGET /api/tasks
POSTBuat data baruPOST /api/tasks
PUT / PATCHUpdate dataPUT /api/tasks/42
DELETEHapus dataDELETE /api/tasks/42

HTTP Status Code Penting

KodeArtiKapan Digunakan
200OKRequest berhasil (GET, PUT)
201CreatedResource baru berhasil dibuat (POST)
204No ContentBerhasil, tidak ada body respons (DELETE)
400Bad RequestInput tidak valid
401UnauthorizedBelum login / token tidak valid
403ForbiddenLogin tapi tidak punya izin
404Not FoundResource tidak ditemukan
422Unprocessable EntityValidasi gagal
500Server ErrorAda bug di server

Langkah 1: Install Sanctum

# Install Sanctum dan buat routes/api.php
php artisan install:api

# Output:
# INFO  Published API routes file.
# INFO  Installed Sanctum...
# Running migrations...

Perintah ini akan:

  • Menambahkan routes/api.php
  • Membuat tabel personal_access_tokens di database
  • Menginstall dan mengkonfigurasi Sanctum

Langkah 2: Update Model User

<?php
// app/Models/User.php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;  // ← Tambahkan ini

class User extends Authenticatable
{
    use HasApiTokens;  // ← Tambahkan trait ini

    // ... kode lainnya
}

Langkah 3: Buat API Controller

# Buat controller khusus untuk API (taruh di subfolder Api/)
php artisan make:controller Api/AuthController
php artisan make:controller Api/TaskController --api
# --api menghasilkan 5 method (tanpa create dan edit yang butuh view)

Auth API Controller

<?php
// app/Http/Controllers/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
{
    // POST /api/register
    public function register(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'name'     => 'required|string|max:255',
            'email'    => 'required|email|unique:users',
            'password' => 'required|string|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([
            'message' => 'Registrasi berhasil!',
            'user'    => $user,
            'token'   => $token,
        ], 201);
    }

    // POST /api/login
    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' => ['Email atau password salah.'],
            ]);
        }

        // Hapus token lama (opsional — untuk keamanan)
        $user->tokens()->delete();

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

        return response()->json([
            'message' => 'Login berhasil!',
            'user'    => $user,
            'token'   => $token,
        ]);
    }

    // POST /api/logout
    public function logout(Request $request): JsonResponse
    {
        $request->user()->currentAccessToken()->delete();

        return response()->json(['message' => 'Logout berhasil!']);
    }

    // GET /api/user
    public function user(Request $request): JsonResponse
    {
        return response()->json($request->user());
    }
}

Task API Controller

<?php
// app/Http/Controllers/Api/TaskController.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;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

class TaskController extends Controller
{
    // GET /api/tasks
    public function index(Request $request): AnonymousResourceCollection
    {
        $tasks = $request->user()
                         ->tasks()
                         ->latest()
                         ->paginate(15);

        return TaskResource::collection($tasks);
    }

    // POST /api/tasks
    public function store(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'title'       => 'required|string|max:255',
            'description' => 'nullable|string',
            'priority'    => 'required|in:low,medium,high',
            'due_date'    => 'nullable|date',
        ]);

        $task = $request->user()->tasks()->create($validated);

        return response()->json(new TaskResource($task), 201);
    }

    // GET /api/tasks/{task}
    public function show(Request $request, Task $task): JsonResponse
    {
        // Pastikan task milik user yang login
        if ($task->user_id !== $request->user()->id) {
            return response()->json(['message' => 'Tidak diizinkan.'], 403);
        }

        return response()->json(new TaskResource($task));
    }

    // PUT /api/tasks/{task}
    public function update(Request $request, Task $task): JsonResponse
    {
        if ($task->user_id !== $request->user()->id) {
            return response()->json(['message' => 'Tidak diizinkan.'], 403);
        }

        $validated = $request->validate([
            'title'       => 'sometimes|string|max:255',
            'description' => 'nullable|string',
            'priority'    => 'sometimes|in:low,medium,high',
            'is_done'     => 'sometimes|boolean',
            'due_date'    => 'nullable|date',
        ]);

        $task->update($validated);

        return response()->json(new TaskResource($task));
    }

    // DELETE /api/tasks/{task}
    public function destroy(Request $request, Task $task): JsonResponse
    {
        if ($task->user_id !== $request->user()->id) {
            return response()->json(['message' => 'Tidak diizinkan.'], 403);
        }

        $task->delete();

        return response()->json(null, 204);
    }
}

Langkah 4: API Resource — Format Response yang Konsisten

API Resource mengontrol bagaimana data Model ditampilkan dalam JSON — kamu bisa menyembunyikan field sensitif, menambahkan field kalkulasi, dll.

php artisan make:resource TaskResource
<?php
// app/Http/Resources/TaskResource.php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class TaskResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id'          => $this->id,
            'title'       => $this->title,
            'description' => $this->description,
            'priority'    => $this->priority,
            'is_done'     => $this->is_done,
            'due_date'    => $this->due_date?->format('Y-m-d'),
            'created_at'  => $this->created_at->toISOString(),
            'updated_at'  => $this->updated_at->toISOString(),
            // Field owner hanya muncul jika relasi di-load
            'owner'       => $this->when(
                $this->relationLoaded('user'),
                fn() => ['id' => $this->user->id, 'name' => $this->user->name]
            ),
        ];
    }
}

Langkah 5: Definisikan Routes API

<?php
// routes/api.php

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

// Public routes (tidak perlu login)
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login',    [AuthController::class, 'login']);

// Protected routes (wajib login dengan token)
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user',    [AuthController::class, 'user']);
    Route::post('/logout', [AuthController::class, 'logout']);

    Route::apiResource('tasks', TaskController::class);
    // Ini membuat 5 route:
    // GET    /api/tasks          → index
    // POST   /api/tasks          → store
    // GET    /api/tasks/{task}   → show
    // PUT    /api/tasks/{task}   → update
    // DELETE /api/tasks/{task}   → destroy
});

Langkah 6: Testing dengan cURL

Kamu tidak perlu Postman untuk testing. cURL bisa dijalankan langsung dari terminal:

# 1. Registrasi user baru
curl -X POST http://localhost:8000/api/register \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"name":"Budi Santoso","email":"[email protected]","password":"password123","password_confirmation":"password123"}'

# Response:
# {
#   "message": "Registrasi berhasil!",
#   "user": {...},
#   "token": "1|abc123xyz..."
# }
# 2. Simpan token untuk digunakan
TOKEN="1|abc123xyz..."  # Ganti dengan token yang kamu dapat

# 3. Ambil semua task
curl http://localhost:8000/api/tasks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
# 4. Buat task baru
curl -X POST http://localhost:8000/api/tasks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"title":"Belajar Laravel API","priority":"high"}'
# 5. Update task (ganti {id} dengan ID task)
curl -X PUT http://localhost:8000/api/tasks/1 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"is_done":true}'
# 6. Hapus task
curl -X DELETE http://localhost:8000/api/tasks/1 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
# 7. Logout
curl -X POST http://localhost:8000/api/logout \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"

Format Response yang Baik

Untuk API production, sebaiknya selalu menggunakan format response yang konsisten:

// Helper untuk response sukses
return response()->json([
    'success' => true,
    'data'    => TaskResource::collection($tasks),
    'meta'    => [
        'total'        => $tasks->total(),
        'per_page'     => $tasks->perPage(),
        'current_page' => $tasks->currentPage(),
    ],
]);

// Helper untuk response error
return response()->json([
    'success' => false,
    'message' => 'Task tidak ditemukan.',
    'errors'  => null,
], 404);

CORS untuk API

Jika API kamu diakses dari domain berbeda (React/Vue app), pastikan konfigurasi CORS sudah benar:

// config/cors.php
'paths'              => ['api/*', 'sanctum/csrf-cookie'],
'allowed_origins'    => ['http://localhost:5173', 'https://myapp.com'],
'allowed_methods'    => ['*'],
'allowed_headers'    => ['*'],
'supports_credentials' => false, // true jika pakai SPA auth

Troubleshooting: Error yang Sering Muncul

401 Unauthenticated Padahal Sudah Kirim Token

Penyebab: Token tidak dikirim dengan format yang benar, atau header Accept: application/json tidak ada (sehingga Laravel redirect ke halaman login HTML bukan JSON).

Solusi:

# Pastikan format header benar:
Authorization: Bearer TOKEN_KAMU_DI_SINI
Accept: application/json

# Jangan ada spasi tambahan, jangan pakai tanda kutip di token

Token Expired atau Tidak Valid Setelah Beberapa Waktu

Penyebab: Token Sanctum bisa dikonfigurasi untuk expire.

Solusi:

// config/sanctum.php
'expiration' => null, // null = tidak pernah expire
// atau
'expiration' => 60 * 24, // expire setelah 24 jam (dalam menit)

Route [login] not defined Saat API Dipanggil

Penyebab: Middleware auth:sanctum me-redirect ke route login yang tidak ada di konteks API, dan header Accept: application/json tidak dikirim.

Solusi:

# Selalu sertakan header ini di semua API request:
Accept: application/json
# Ini memberitahu Laravel untuk return JSON error, bukan redirect

Pertanyaan yang Sering Diajukan

Apa bedanya Sanctum vs Passport?

Sanctum adalah solusi ringan untuk API token dan SPA auth — cocok untuk 99% kasus. Passport adalah implementasi OAuth2 lengkap — cocok jika kamu membangun platform yang mengizinkan aplikasi pihak ketiga (seperti sistem “Login with Google” kamu sendiri). Untuk proyek biasa, selalu gunakan Sanctum.

Bagaimana cara handle pagination di API?

Laravel secara otomatis memformat hasil paginate() menjadi JSON yang baik dengan informasi halaman:

{
  "data": [...],
  "links": {
    "first": "http://localhost:8000/api/tasks?page=1",
    "next":  "http://localhost:8000/api/tasks?page=2"
  },
  "meta": {
    "current_page": 1,
    "total": 50,
    "per_page": 15
  }
}

Apakah bisa membuat API versioning di Laravel?

Bisa! Tambahkan prefix versi di routes:

// routes/api.php
Route::prefix('v1')->group(function () {
    Route::apiResource('tasks', Api\V1\TaskController::class);
});

Route::prefix('v2')->group(function () {
    Route::apiResource('tasks', Api\V2\TaskController::class);
});

// Akses: /api/v1/tasks atau /api/v2/tasks

Kesimpulan

Task Manager kita sekarang sudah menjadi API yang bisa dikonsumsi oleh aplikasi mobile, SPA React/Vue, atau layanan apapun yang butuh data task!

Yang sudah kita bangun:

  • Sanctum API Token: register, login, logout yang aman
  • CRUD API Endpoint yang dilindungi dengan auth:sanctum
  • API Resource untuk format JSON yang konsisten
  • Authorization agar setiap user hanya bisa akses datanya sendiri
  • Testing dengan cURL tanpa perlu tools tambahan

Di artikel terakhir seri ini, kita akan belajar hal yang paling ditunggu-tunggu oleh banyak developer Indonesia: Deploy Aplikasi Laravel ke VPS Ubuntu — dari zero hingga production dengan Nginx dan SSL!

Kamu hampir menyelesaikan seri ini! Satu artikel lagi! 🚀

Artikel Terkait