REST API CRUD dengan Laravel Sanctum
REST API CRUD Laravel Sanctum - Laravel Sanctum adalah salah satu package autentikasi dari Laravel yang dirancang khusus untuk menyediakan layanan autentikasi dan otorisasi untuk aplikasi berbasis RESTful API. Dalam artikel ini, kita akan membahas bagaimana mengimplementasikan operasi CRUD (Create, Read, Update, Delete) menggunakan Laravel Sanctum untuk mengamankan API kita.
Table of Contents
Pengenalan
Sebelum memulai, mari kita pahami beberapa konsep utama yang akan digunakan dalam implementasi API CRUD dengan Laravel Sanctum.
1. REST API: REST (Representational State Transfer) adalah pendekatan arsitektur untuk mengelola komunikasi antara sistem yang berbeda. API RESTful menggunakan metode HTTP seperti GET, POST, PUT, DELETE untuk mengakses dan mengelola sumber daya.
2. CRUD: CRUD merupakan akronim untuk Create, Read, Update, dan Delete. Ini adalah empat operasi dasar yang digunakan untuk mengelola data dalam sistem.
3. Laravel Sanctum: Laravel Sanctum adalah paket autentikasi yang dikembangkan oleh Laravel yang menyediakan autentikasi berbasis token untuk aplikasi berbasis API. Ini memungkinkan kita untuk melindungi rute tertentu dalam API kita dengan menggunakan token autentikasi.
Pada artikel ini, saya akan melanjutkan materi atau topik dari artikel sebelumnya tentang penggunakan sanctum package di laravel. Pada artikel ini saya akan share tentang bagaimana cara membuat REST API CRUD menggunakan sanctum package di laravel 10.
Jika kamu belum mengikuti artikel laravel sanctum yang telah dibagikan sebelumnya, saya sarankan untuk mengikutinya terlebih dahulu. Karena pada artikel ini saya tidak akan menjelaskan dari awal lagi atau mulai dari bagaimana cara mendapatkan token.
Baiklah, mari kita langsung saja ke langkah-langkahnya di bawah ini 👇
Step 1: Setup Model & Migration
Oke, pertama kita perlu membuat model dan migration baru. Jalankan perintah artisan seperti di bawah untuk generate class Post model dan posts migration.
php artisan make:model Post -m Kemudian, buka file posts migration yang baru saja digenerate tersebut. File tersebut terletak di direktori database/migrations/{timestamp}_create_posts_table.php. Edit method up pada file tersebut menjadi seperti kode di bawah ini. Disini, kita menambahkan field user_id yang merupakan foreign key dengan table users.
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('title');
$table->string('slug')->unique();
$table->longText('content');
$table->string('cover')->nullable();
$table->boolean('is_published')->default(false);
$table->timestamps();
});
} Jika file posts migration sudah disetup, lanjutkan dengan menjalankan perintah php artisan migrate.
php artisan migrate Berikutnya, lanjutkan dengan menyesuaikan file Post model. Tambahkan method $guarded = [] dan beberapa method lainnya seperti contoh di bawah ini.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Post extends Model
{
use HasFactory;
protected $guarded = [];
protected static function booted()
{
static::creating(function ($post) {
$post->slug = \Str::slug($post->title);
$latestSlug =
static::whereRaw("slug RLIKE '^{$post->slug}(-[0-9]*)?$'")
->latest('id')
->pluck('slug')
->first();
if ($latestSlug) {
$pieces = explode('-', $latestSlug);
$number = intval(end($pieces));
$post->slug .= '-' . ($number + 1);
}
});
static::updating(function ($post) {
$post->slug = \Str::slug($post->title);
$latestSlug =
static::whereRaw("slug RLIKE '^{$post->slug}(-[0-9]*)?$'")
->latest('id')
->pluck('slug')
->first();
if ($latestSlug) {
$pieces = explode('-', $latestSlug);
$number = intval(end($pieces));
$post->slug .= '-' . ($number + 1);
}
});
}
public function getCoverPathAttribute()
{
return $this->cover ? asset('storage/' . $this->cover) : 'https://via.placeholder.com/640x480.png/00ff77?text=No+Image';
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
} Perhatikan kode di atas. Pada Post model, kita menambahkan method booted dengan fungsi untuk melakukan generate unique slug pada saat insert dan update data post.
Kita juga mendifinisikan accessor. Accessor merupakan method pada model Laravel yang memungkinkan kita untuk mengakses atau memanipulasi attribute value (column) dari model sebelum nilai tersebut diakses oleh kode lain. Singkatnya, Accessor memungkinkan kita untuk melakukan transformasi data pada attribute value sebelum value tersebut dikembalikan ke luar dari model.
Pada contoh di atas, kita mempunyai attribute cover pada model "Post" dan kita ingin menambahkan attribute cover_path yang nilainya merupakan full path dari data cover (jika data tersebut memiliki cover value), dan jika data tersebut tidak mempunya cover value, maka kita return value "https://via.placeholder.com/640x480.png/00ff77?text=No+Image"
Selain itu, kita juga mendefinisikan relationship method pada child model (Post) dengan belongsTo method yang mengarah ke model User.
Kemudian buka file model User dan menambahkan one-to-many relationship dengan model Post. One-to-many relationship digunakan untuk mendefinisikan relasi dimana single model merupakan parent dari satu atau lebih child model. Misalnya, User yang mungkin memiliki jumlah post yang tak terbatas.
use Illuminate\Database\Eloquent\Relations\HasMany;
....
....
....
public function posts(): HasMany
{
return $this->hasMany(Post::class);
} Step 2: Setup PostResource
Seperti pada artikel sebelumnya, pada artikel ini saya juga akan mengimplementasikan eloquent api resources. Untuk membuat file api resources, kita bisa menjalankan perintah artisan seperti di bawah ini. Perintah tersebut akan membuatkan kita file PostResource dan terletak didirektori app/Http/Resources.
php artisan make:resource PostResource Buka file yang baru saja digenerate tersebut, pada function toArray silahkan ubah menjadi seperti contoh kode di bawah ini. Jika kamu ingin menampilkan nama user yang membuat post tersebut, kamu bisa menambahkan 'author' => $this->user->name.
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'author' => $this->user->name,
'title' => $this->title,
'slug' => $this->slug,
'content' => $this->content,
'cover' => $this->cover_path,
'is_published' => $this->is_published ? true : false,
'created_at' => $this->created_at->format('Y-m-d H:i:s'),
'updated_at' => $this->updated_at->format('Y-m-d H:i:s')
];
} Step 3: Setup PostController
Pada step ketiga ini, kita perlu membuat controller baru. Silakan jalankan perintah artisan seperti di bawah ini untuk membuat file PostController.php.
php artisan make:controller Api/PostController Jika file atau class PostController sudah berhasil dibuat, buka file tersebut dan tambahkan kode seperti di bawah ini.
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\PostResource;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class PostController extends Controller
{
public function index(Request $request){
try {
$posts = $request->user()->posts()->when($request->keyword, function ($query) use ($request) {
$query->where('title', 'like', "%{$request->keyword}%");
})->latest()->paginate(10);
return response()->json([
'message' => 'Success',
'data' => PostResource::collection($posts)
], 200);
} catch (\Throwable $th) {
return response()->json([
'message' => 'Something went wrong'
], 500);
}
}
public function show(Request $request, $id){
try {
$post = $request->user()->posts()->firstWhere('id', $id);
if (!$post) {
return response()->json([
'message' => 'Data Not found'
], 404);
}
return response()->json([
'message' => 'Success',
'data' => new PostResource($post)
], 200);
} catch (\Throwable $th) {
return response()->json([
'message' => $th->getMessage()
], 500);
}
}
public function store(Request $request){
try {
$validator = Validator::make($request->all(), [
'title' => 'required|string|max:255',
'content' => 'required|string',
]);
if ($validator->fails()) {
return response()->json([
'message' => 'Validation errors',
'errors' => $validator->errors()
], 422);
}
$cover = $request->file('cover');
if ($cover) {
$file_name = 'post-' . time() . '.' . $cover->getClientOriginalExtension();
$file_path = $cover->storeAs('posts', $file_name, 'public');
}
$post = $request->user()->posts()->create([
'title' => $request->title,
'content' => $request->content,
'cover' => $file_path ?? null,
'is_published' => $request->is_published,
]);
return response()->json([
'message' => 'Success',
'data' => new PostResource($post)
], 201);
} catch (\Throwable $th) {
return response()->json([
'message' => $th->getMessage()
], 500);
}
}
public function update(Request $request, $id){
try {
$validator = Validator::make($request->all(), [
'title' => 'required|string|max:255',
'content' => 'required|string',
]);
if ($validator->fails()) {
return response()->json([
'message' => 'Validation errors',
'errors' => $validator->errors()
], 422);
}
$post = $request->user()->posts()->firstWhere('id', $id);
if (!$post) {
return response()->json([
'message' => 'Data Not found'
], 404);
}
$cover = $request->file('cover');
if ($cover) {
if ($post->cover && Storage::disk('public')->exists($post->cover)) {
Storage::disk('public')->delete($post->cover);
}
$file_name = 'post-' . time() . '.' . $cover->getClientOriginalExtension();
$file_path = $cover->storeAs('posts', $file_name, 'public');
}
$post->update([
'title' => $request->title,
'content' => $request->content,
'cover' => $file_path ?? $post->cover,
'is_published' => $request->is_published,
]);
return response()->json([
'message' => 'Success',
'data' => new PostResource($post)
], 200);
} catch (\Throwable $th) {
return response()->json([
'message' => $th->getMessage()
], 500);
}
}
public function destroy(Request $request, $id){
try {
$post = $request->user()->posts()->firstWhere('id', $id);
if (!$post) {
return response()->json([
'message' => 'Data Not found'
], 404);
}
if ($post->cover && Storage::disk('public')->exists($post->cover)) {
Storage::disk('public')->delete($post->cover);
}
$post->delete();
return response()->json([
'message' => 'Success',
], 200);
} catch (\Throwable $th) {
return response()->json([
'message' => $th->getMessage()
], 500);
}
}
} Berikut ini adalah penjelasan dari kode di atas.
Index
Method pertama yang akan kita bahas adalah method index. Method ini berfungsi untuk mengambil data dari table posts yang mempunyai value user_id sama dengan id dari user yang mengakses. Disini kita bisa menggunakan eager loading atau mengambil data dari relasi antara model User dengan Post. Kita juga menambahkan kondisi jika ada request keyword maka akan mencari data yang mempunyai title value yang mirip dengan keyword value. Data yang diambil kemudian ditampilkan menggunakan class PostResource.
public function index(Request $request){
try {
$posts = $request->user()->posts()->when($request->keyword, function ($query) use ($request) {
$query->where('title', 'like', "%{$request->keyword}%");
})->latest()->paginate(10);
return response()->json([
'message' => 'Success',
'data' => PostResource::collection($posts)
], 200);
} catch (\Throwable $th) {
return response()->json([
'message' => 'Something went wrong'
], 500);
}
} Show
Berikutnya adalah method show. Method ini berfungsi untuk menampilkan detail post dengan parameter id dan menampilkannya dalam class PostResource.
public function show(Request $request, $id){
try {
$post = $request->user()->posts()->firstWhere('id', $id);
if (!$post) {
return response()->json([
'message' => 'Data Not found'
], 404);
}
return response()->json([
'message' => 'Success',
'data' => new PostResource($post)
], 200);
} catch (\Throwable $th) {
return response()->json([
'message' => $th->getMessage()
], 500);
}
} Store
function store, seperti pada umumnya, function ini berfungsi untuk menangkap request yang dikirimkan dan akan menambahkannya ke table di database. Untuk file cover, akan kita tempatkan di folder posts.
public function store(Request $request){
try {
$validator = Validator::make($request->all(), [
'title' => 'required|string|max:255',
'content' => 'required|string',
]);
if ($validator->fails()) {
return response()->json([
'message' => 'Validation errors',
'errors' => $validator->errors()
], 422);
}
$cover = $request->file('cover');
if ($cover) {
$file_name = 'post-' . time() . '.' . $cover->getClientOriginalExtension();
$file_path = $cover->storeAs('posts', $file_name, 'public');
}
$post = $request->user()->posts()->create([
'title' => $request->title,
'content' => $request->content,
'cover' => $file_path ?? null,
'is_published' => $request->is_published,
]);
return response()->json([
'message' => 'Success',
'data' => new PostResource($post)
], 201);
} catch (\Throwable $th) {
return response()->json([
'message' => $th->getMessage()
], 500);
}
} Update
Function update ini berfungsi untuk memperbarui data post dengan id tertentu. Jika ada request cover baru, maka akan menghapus file cover lama di storage dan akan menambahkan file cover baru.
public function update(Request $request, $id){
try {
$validator = Validator::make($request->all(), [
'title' => 'required|string|max:255',
'content' => 'required|string',
]);
if ($validator->fails()) {
return response()->json([
'message' => 'Validation errors',
'errors' => $validator->errors()
], 422);
}
$post = $request->user()->posts()->firstWhere('id', $id);
if (!$post) {
return response()->json([
'message' => 'Data Not found'
], 404);
}
$cover = $request->file('cover');
if ($cover) {
if ($post->cover && Storage::disk('public')->exists($post->cover)) {
Storage::disk('public')->delete($post->cover);
}
$file_name = 'post-' . time() . '.' . $cover->getClientOriginalExtension();
$file_path = $cover->storeAs('posts', $file_name, 'public');
}
$post->update([
'title' => $request->title,
'content' => $request->content,
'cover' => $file_path ?? $post->cover,
'is_published' => $request->is_published,
]);
return response()->json([
'message' => 'Success',
'data' => new PostResource($post)
], 200);
} catch (\Throwable $th) {
return response()->json([
'message' => $th->getMessage()
], 500);
}
} Destroy
Dan yang terakhir adalah function destroy. Function ini berfungsi untuk menghapus file cover di storage dan data post dengan parameter id tertentu.
public function destroy(Request $request, $id){
try {
$post = $request->user()->posts()->firstWhere('id', $id);
if (!$post) {
return response()->json([
'message' => 'Data Not found'
], 404);
}
if ($post->cover && Storage::disk('public')->exists($post->cover)) {
Storage::disk('public')->delete($post->cover);
}
$post->delete();
return response()->json([
'message' => 'Success',
], 200);
} catch (\Throwable $th) {
return response()->json([
'message' => $th->getMessage()
], 500);
}
} Step 4: Setup Route
Oke, setelah selesai selesai di PostController, selanjutnya kita perlu menambahkan route baru. Buka file routes/api.php, lalu tambahkan route resource seperti kode di bawah ini. Dan jangan lupa untuk mengimport class PostController.
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('posts', PostController::class);
}); Step 5: Testing
Setelah mengimplementasikan operasi CRUD dan menambahkan autentikasi Sanctum pada API, langkah terakhir adalah menguji API kita untuk memastikan semuanya berfungsi dengan benar. Kita dapat menggunakan aplikasi pihak ketiga seperti Postman atau Insomnia untuk menguji API.
Catatan: Pastikan kamu telah mendapatkan token autentikasi dengan mengirimkan request autentikasi ke
/api/loginmenggunakan alamat email, password dan OTP code. Dapatkan token dari response dan gunakan token tersebut untuk mengakses route yang memerlukan autentikasi, seperti operasi CRUD pada entitasPosts.
Index
Yang pertama adalah fungsi index atau get posts. Silahkan buka postman, cobalah untuk login untuk mendapatkan token. Jika kamu sudah mendapatkan token, kamu bisa membuat folder baru dengan nama misalnya "Posts", kemudian tambahkan request baru dengan nama index. Pilih method GET dan isi URL dengan 127.0.0.1:{port}/api/posts atau laravel-10-sanctum.test/api/posts. Pada tab Authorization, pilih type bearer token, lalu inputkan token yang kamu dapatkan setelah berhasil login.
Silahkan coba klik Send, maka hasilnya akan seperti gambar di bawah ini atau hanyalah empty array (jika belum melakukan input post). Pada step ini, kita juga bisa mencoba fitur search dengan menambahkan params dengan key "keyword".

Store
Oke, mari kita lanjutkan dengan mencoba fitur create post. Buat request baru dengan nama misalnya store, pilih method POST dan isi URL dengan seperti gambar di bawah ini. gunakan method POST dan URL 127.0.0.1:{port}/api/posts atau laravel-10-sanctum.test/api/posts. Pada tab Authorization, jangan lupa ditambahkan token.
Kemudian, lanjut pada body, tambahkan key untuk cover, title, content dan is_published serta masing-masing valuenya. Jika sudah menambahkan key dan value, klik Send. Jika berhasil, maka response yang ditampilkan akan seperti gambar di bawah ini.

Update
Selanjutnya, kita akan mencoba fitur update. Silahkan buat request baru dengan method POST, isi URL dengan posts/{post_id} dan jangan lupa untuk menambahkan token. Pada tab body, silahkan tambahkan key _method yang bernilai PUT dan key-key lain yang ingin diperbarui pada data post tersebut. Kemudian klik Send, maka data post dengan id tersebut akan berubah, dan jika dicek pada storage/app/public/posts file cover sebelumnya akan berganti dengan file cover baru.

Delete
Terakhir, mari kita coba fitur delete. Buat request baru dengan method DELETE, isi URL dengan /posts/{post_id} dan jangan lupa untuk menambahkan token. Jika data post dengan id tersebut merupakan post yang dibuat oleh akunmu, maka ketika diklik Send data post dan file cover akan terhapus. Namun jika post dengan id tersebut bukan milik kamu, maka response yang ditampilkan "Data not found".

Dengan mengikuti panduan ini, kita telah berhasil mengimplementasikan REST API CRUD dengan Laravel Sanctum. Kini API kita memiliki operasi CRUD yang aman dengan dukungan autentikasi token Sanctum, sehingga dapat diandalkan untuk kebutuhan aplikasi modern berbasis API. Selamat mencoba! 👨💻 🚀
Source Code: Laravel 10 Sanctum
- REST API Authentication dengan Laravel Sanctum
- REST API CRUD dengan Laravel Sanctum
- Generate Unique Slug di Laravel dengan Eloquent Sluggable Package
- Implementasi Request Validation di Laravel REST API
- Implementasi API Versioning di Laravel
- Membuat Admin Panel dengan Filament - Tutorial Lengkap untuk Pemula
- Tutorial : Menggunakan DD ( ) - Di Laravel
- Tutorial : Membuat API Resources Di Laravel 10
- Tutorial : Membuat-Model Dan Migration-Di Laravel 10
- Laravel 10
- Laravel Restful Api
- Tutorial: Membuat Eloquent Accessor Di Laravel 10
- Tutorial : Menampilkan Data dari Database Di Laravel 10
- Tutorial: Cara Memasukan Data Ke Dalam Database Di Laravel 10
- Tutorial: Menampilkan Detail Data Di Laravel 10
- Tutorial: Update Data Ke Dalam Database Di Laravel 10
- Tutorial: Membuat WhatsApp Menggunakan Laravel
- Laravel Midtrans Tutorial
- Sweet Alert Laravel 10
Last updated on June 03, 2026