🏠 Home / Hub
🧱 Projects 03 — Laravel API Backend
← Projects Menu
Goal: Vue Dashboard (Project 02) နဲ့ Flutter Mobile (Project 04) တို့ connect လုပ်မယ့် REST API တစ်ခုဆောက်မယ်။ Products CRUD + Auth (Sanctum) + proper JSON responses ပါမယ်။
1. API Endpoints Plan
| Method | Endpoint | Description | Auth |
| POST | /api/auth/register | Register new user | No |
| POST | /api/auth/login | Login, get token | No |
| POST | /api/auth/logout | Revoke token | Yes |
| GET | /api/auth/me | Current user info | Yes |
| GET | /api/products | List (search, filter, paginate) | Yes |
| POST | /api/products | Create product | Yes |
| GET | /api/products/{id} | Single product | Yes |
| PUT | /api/products/{id} | Update product | Yes |
| DELETE | /api/products/{id} | Delete product | Yes |
| GET | /api/categories | List categories | Yes |
2. Laravel Setup
composer create-project laravel/laravel product-api
cd product-api
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
# .env
DB_CONNECTION=mysql # or pgsql for PostgreSQL
DB_DATABASE=product_api
DB_USERNAME=root
DB_PASSWORD=secret
# config/cors.php
'allowed_origins' => ['http://localhost:5173'], # Vite dev server
3. Migrations
# products migration
php artisan make:migration create_products_table
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 10, 2);
$table->integer('stock')->default(0);
$table->string('image')->nullable();
$table->boolean('is_active')->default(true);
$table->foreignId('category_id')->nullable()->constrained()->nullOnDelete();
$table->foreignId('user_id')->constrained(); // created by
$table->timestamps();
$table->softDeletes(); // enables deleted_at (recoverable)
});
# categories migration
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
4. Product Model
// app/Models/Product.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Product extends Model
{
use SoftDeletes;
protected $fillable = [
'name', 'description', 'price', 'stock',
'image', 'is_active', 'category_id', 'user_id'
];
protected $casts = [
'price' => 'decimal:2',
'is_active' => 'boolean',
];
// Relationships
public function category() {
return $this->belongsTo(Category::class);
}
public function creator() {
return $this->belongsTo(User::class, 'user_id');
}
}
5. API Resource (Transformer)
php artisan make:resource ProductResource
// app/Http/Resources/ProductResource.php
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'price' => (float) $this->price,
'stock' => $this->stock,
'is_active' => $this->is_active,
'image_url' => $this->image ? asset('storage/' . $this->image) : null,
'category' => $this->whenLoaded('category', fn() => [
'id' => $this->category->id,
'name' => $this->category->name,
]),
'created_at' => $this->created_at->toISOString(),
];
}
// Collection Resource
php artisan make:resource ProductCollection --collection
// or just: ProductResource::collection($products)
6. ProductController (API)
php artisan make:controller Api/ProductController --resource
// app/Http/Controllers/Api/ProductController.php
public function index(Request $request)
{
$query = Product::with('category')
->where('is_active', true)
->when($request->search, fn($q, $s) =>
$q->where('name', 'like', "%{$s}%")
->orWhere('description', 'like', "%{$s}%")
)
->when($request->category_id, fn($q, $id) =>
$q->where('category_id', $id)
)
->orderBy($request->sort_by ?? 'created_at', $request->sort ?? 'desc');
return ProductResource::collection($query->paginate(15));
}
public function store(Request $request)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
'price' => 'required|numeric|min:0',
'stock' => 'integer|min:0',
'category_id' => 'nullable|exists:categories,id',
]);
$data['user_id'] = auth()->id();
$product = Product::create($data);
return new ProductResource($product->load('category'));
}
public function update(Request $request, Product $product)
{
$data = $request->validate([...]); // same rules
$product->update($data);
return new ProductResource($product->load('category'));
}
public function destroy(Product $product)
{
$product->delete(); // soft delete
return response()->json(['message' => 'Deleted']);
}
7. Auth Controller (Sanctum)
// app/Http/Controllers/Api/AuthController.php
public function register(Request $request)
{
$data = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:8|confirmed',
]);
$data['password'] = bcrypt($data['password']);
$user = User::create($data);
$token = $user->createToken('api-token')->plainTextToken;
return response()->json(['token' => $token, 'user' => $user], 201);
}
public function login(Request $request)
{
$creds = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (!auth()->attempt($creds)) {
return response()->json(['message' => 'Invalid credentials'], 401);
}
$token = auth()->user()->createToken('api-token')->plainTextToken;
return response()->json(['token' => $token, 'user' => auth()->user()]);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Logged out']);
}
// routes/api.php
Route::prefix('auth')->group(function () {
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::get('me', [AuthController::class, 'me']);
});
});
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('products', ProductController::class);
Route::apiResource('categories', CategoryController::class);
});
8. Testing with curl / Postman
# Register
curl -X POST http://localhost:8000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Mg Mg","email":"mg@test.com","password":"password","password_confirmation":"password"}'
# Login
curl -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"mg@test.com","password":"password"}'
# response: {"token":"1|xxxx..."}
# Get products (with token)
curl http://localhost:8000/api/products \
-H "Authorization: Bearer 1|xxxx..."
# Create product
curl -X POST http://localhost:8000/api/products \
-H "Authorization: Bearer 1|xxxx..." \
-H "Content-Type: application/json" \
-d '{"name":"Phone","price":299.99,"stock":50}'
📌 Study Checklist
- အပေါ်က concept ကို တစ်ကြောင်းချင်းဖတ်ပြီး example ကို ကိုယ်တိုင်ပြန်ရေးကြည့်ပါ။
- Code/command ပါတဲ့ lesson ဆိုရင် value/name/path တစ်ခုခု ပြောင်းပြီး result ဘာကွာလဲ စမ်းပါ။
- မမှတ်မိသေးတဲ့ keyword 3 ခုကို notebook ထဲရေးပြီး နောက် lesson မသွားခင် ပြန်ရှင်းကြည့်ပါ။
- ပြီးရင် Home / Hub ကိုပြန်သွားပြီး next lesson ဆက်သင်ပါ။