<?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::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content')->nullable();
$table->string('status')->default('draft'); // draft, published
$table->foreignId('author_id')->constrained('users')->cascadeOnDelete();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
//Model
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title',
'content',
'status',
'author_id',
];
// Author relationship
public function author() {
return $this->belongsTo(User::class, 'author_id');
}
}
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Post;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class PostController extends Controller
{
// Save draft
public function saveDraft(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required|string|max:255',
'content' => 'nullable|string',
]);
if ($validator->fails()) {
return response()->json([
'status' => 'error',
'message' => $validator->errors(),
], 422);
}
$post = Post::updateOrCreate(
['id' => $request->id], // update if ID exists
[
'title' => $request->title,
'content' => $request->content ?? '',
'status' => 'draft',
'author_id' => Auth::id() ?? 1, // Auth user or default
]
);
return response()->json([
'status' => 'success',
'message' => 'Post saved as draft',
'data' => [
'id' => $post->id,
'title' => $post->title,
'content' => $post->content,
'status' => $post->status,
'author_id' => $post->author_id,
'created_at' => $post->created_at,
'updated_at' => $post->updated_at,
],
]);
}
}
<?php
use App\Http\Controllers\Api\PostController;
Route::middleware('auth:sanctum')->group(function () {
Route::post('/posts/draft', [PostController::class, 'saveDraft']);
});
<?php
<template>
<div class="p-4">
<input v-model="title" placeholder="Post Title" class="border p-2 w-full mb-2"/>
<textarea v-model="content" placeholder="Post Content" class="border p-2 w-full mb-2"></textarea>
<button @click="saveDraft" class="bg-blue-500 text-white px-4 py-2 rounded">Save Draft</button>
<div v-if="response" class="mt-4 p-2 border">
<pre>{{ response }}</pre>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios';
const title = ref('');
const content = ref('');
const response = ref(null);
const saveDraft = async () => {
try {
const res = await axios.post('/api/posts/draft', {
title: title.value,
content: content.value,
});
response.value = res.data;
} catch (err) {
response.value = err.response?.data || err.message;
}
};
</script>
<style scoped>
/* optional tailwind styling already used */
</style>
Features: