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
| Method | Aksi | Contoh |
|---|---|---|
GET | Ambil data | GET /api/tasks |
POST | Buat data baru | POST /api/tasks |
PUT / PATCH | Update data | PUT /api/tasks/42 |
DELETE | Hapus data | DELETE /api/tasks/42 |
HTTP Status Code Penting
| Kode | Arti | Kapan Digunakan |
|---|---|---|
200 | OK | Request berhasil (GET, PUT) |
201 | Created | Resource baru berhasil dibuat (POST) |
204 | No Content | Berhasil, tidak ada body respons (DELETE) |
400 | Bad Request | Input tidak valid |
401 | Unauthorized | Belum login / token tidak valid |
403 | Forbidden | Login tapi tidak punya izin |
404 | Not Found | Resource tidak ditemukan |
422 | Unprocessable Entity | Validasi gagal |
500 | Server Error | Ada 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_tokensdi 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! 🚀