{"id":27,"date":"2026-07-06T14:20:00","date_gmt":"2026-07-06T08:50:00","guid":{"rendered":"https:\/\/softcrony.com\/blog\/?p=27"},"modified":"2026-07-06T14:20:00","modified_gmt":"2026-07-06T08:50:00","slug":"laravel-12-rest-api-complete-guide","status":"publish","type":"post","link":"https:\/\/softcrony.com\/blog\/laravel-12-rest-api-complete-guide\/","title":{"rendered":"Building a REST API with Laravel 12: Complete Guide for Beginners"},"content":{"rendered":"<p>Laravel makes building REST APIs surprisingly straightforward. In this guide we&#8217;ll build a complete, production-ready API from scratch using Laravel 12 \u2014 covering everything from setup to authentication.<\/p>\n<p>By the end you&#8217;ll have a fully working API that follows REST best practices and is ready to connect to any frontend \u2014 React, Vue, mobile app, or third-party service.<\/p>\n<h2>What We&#8217;re Building<\/h2>\n<ul>\n<li>Full CRUD operations (Create, Read, Update, Delete)<\/li>\n<li>API authentication using Laravel Sanctum<\/li>\n<li>API Resources for consistent response formatting<\/li>\n<li>Request validation<\/li>\n<li>Proper HTTP status codes and error handling<\/li>\n<\/ul>\n<h2>Prerequisites<\/h2>\n<ul>\n<li>PHP 8.2+<\/li>\n<li>Composer installed<\/li>\n<li>Basic understanding of PHP and MVC concepts<\/li>\n<li>Postman or Hoppscotch for testing<\/li>\n<\/ul>\n<h2>Step 1 \u2014 Create a New Laravel 12 Project<\/h2>\n<pre><code>composer create-project laravel\/laravel taskapi\r\ncd taskapi<\/code><\/pre>\n<p>Set up your database in <code>.env<\/code>:<\/p>\n<pre><code>DB_CONNECTION=mysql\r\nDB_HOST=127.0.0.1\r\nDB_PORT=3306\r\nDB_DATABASE=taskapi\r\nDB_USERNAME=root\r\nDB_PASSWORD=<\/code><\/pre>\n<h2>Step 2 \u2014 Create the Task Model and Migration<\/h2>\n<pre><code>php artisan make:model Task -m<\/code><\/pre>\n<p>Open the migration file and define the schema:<\/p>\n<pre><code>&lt;?php\r\n\r\nuse Illuminate\\Database\\Migrations\\Migration;\r\nuse Illuminate\\Database\\Schema\\Blueprint;\r\nuse Illuminate\\Support\\Facades\\Schema;\r\n\r\nreturn new class extends Migration\r\n{\r\n    public function up(): void\r\n    {\r\n        Schema::create('tasks', function (Blueprint $table) {\r\n            $table-&gt;id();\r\n            $table-&gt;foreignId('user_id')-&gt;constrained()-&gt;onDelete('cascade');\r\n            $table-&gt;string('title');\r\n            $table-&gt;text('description')-&gt;nullable();\r\n            $table-&gt;enum('status', ['pending', 'in_progress', 'completed'])-&gt;default('pending');\r\n            $table-&gt;timestamp('due_date')-&gt;nullable();\r\n            $table-&gt;timestamps();\r\n        });\r\n    }\r\n\r\n    public function down(): void\r\n    {\r\n        Schema::dropIfExists('tasks');\r\n    }\r\n};<\/code><\/pre>\n<pre><code>php artisan migrate<\/code><\/pre>\n<h2>Step 3 \u2014 Set Up the Task Model<\/h2>\n<pre><code>&lt;?php\r\n\r\nnamespace App\\Models;\r\n\r\nuse Illuminate\\Database\\Eloquent\\Model;\r\nuse Illuminate\\Database\\Eloquent\\Relations\\BelongsTo;\r\n\r\nclass Task extends Model\r\n{\r\n    protected $fillable = [\r\n        'title',\r\n        'description',\r\n        'status',\r\n        'due_date',\r\n        'user_id',\r\n    ];\r\n\r\n    protected $casts = [\r\n        'due_date' =&gt; 'datetime',\r\n    ];\r\n\r\n    public function user(): BelongsTo\r\n    {\r\n        return $this-&gt;belongsTo(User::class);\r\n    }\r\n}<\/code><\/pre>\n<h2>Step 4 \u2014 Install Laravel Sanctum<\/h2>\n<p>Laravel 12 includes Sanctum by default. Run:<\/p>\n<pre><code>php artisan vendor:publish --provider=\"Laravel\\Sanctum\\SanctumServiceProvider\"\r\nphp artisan migrate<\/code><\/pre>\n<p>Add <code>HasApiTokens<\/code> to your User model:<\/p>\n<pre><code>use Laravel\\Sanctum\\HasApiTokens;\r\n\r\nclass User extends Authenticatable\r\n{\r\n    use HasApiTokens;\r\n}<\/code><\/pre>\n<h2>Step 5 \u2014 Create an API Resource<\/h2>\n<pre><code>php artisan make:resource TaskResource<\/code><\/pre>\n<pre><code>&lt;?php\r\n\r\nnamespace App\\Http\\Resources;\r\n\r\nuse Illuminate\\Http\\Request;\r\nuse Illuminate\\Http\\Resources\\Json\\JsonResource;\r\n\r\nclass TaskResource extends JsonResource\r\n{\r\n    public function toArray(Request $request): array\r\n    {\r\n        return [\r\n            'id'          =&gt; $this-&gt;id,\r\n            'title'       =&gt; $this-&gt;title,\r\n            'description' =&gt; $this-&gt;description,\r\n            'status'      =&gt; $this-&gt;status,\r\n            'due_date'    =&gt; $this-&gt;due_date?-&gt;toDateString(),\r\n            'created_at'  =&gt; $this-&gt;created_at-&gt;toDateTimeString(),\r\n        ];\r\n    }\r\n}<\/code><\/pre>\n<h2>Step 6 \u2014 Create the Task Controller<\/h2>\n<pre><code>php artisan make:controller Api\/TaskController --api<\/code><\/pre>\n<pre><code>&lt;?php\r\n\r\nnamespace App\\Http\\Controllers\\Api;\r\n\r\nuse App\\Http\\Controllers\\Controller;\r\nuse App\\Http\\Resources\\TaskResource;\r\nuse App\\Models\\Task;\r\nuse Illuminate\\Http\\Request;\r\nuse Illuminate\\Http\\JsonResponse;\r\n\r\nclass TaskController extends Controller\r\n{\r\n    public function index(Request $request)\r\n    {\r\n        $tasks = $request-&gt;user()-&gt;tasks()-&gt;latest()-&gt;paginate(10);\r\n        return TaskResource::collection($tasks);\r\n    }\r\n\r\n    public function store(Request $request): TaskResource\r\n    {\r\n        $validated = $request-&gt;validate([\r\n            'title'       =&gt; 'required|string|max:255',\r\n            'description' =&gt; 'nullable|string',\r\n            'status'      =&gt; 'in:pending,in_progress,completed',\r\n            'due_date'    =&gt; 'nullable|date',\r\n        ]);\r\n\r\n        $task = $request-&gt;user()-&gt;tasks()-&gt;create($validated);\r\n        return new TaskResource($task);\r\n    }\r\n\r\n    public function show(Request $request, Task $task): TaskResource\r\n    {\r\n        $this-&gt;authorize('view', $task);\r\n        return new TaskResource($task);\r\n    }\r\n\r\n    public function update(Request $request, Task $task): TaskResource\r\n    {\r\n        $this-&gt;authorize('update', $task);\r\n\r\n        $validated = $request-&gt;validate([\r\n            'title'       =&gt; 'sometimes|string|max:255',\r\n            'description' =&gt; 'nullable|string',\r\n            'status'      =&gt; 'in:pending,in_progress,completed',\r\n            'due_date'    =&gt; 'nullable|date',\r\n        ]);\r\n\r\n        $task-&gt;update($validated);\r\n        return new TaskResource($task);\r\n    }\r\n\r\n    public function destroy(Request $request, Task $task): JsonResponse\r\n    {\r\n        $this-&gt;authorize('delete', $task);\r\n        $task-&gt;delete();\r\n        return response()-&gt;json(['message' =&gt; 'Task deleted successfully']);\r\n    }\r\n}<\/code><\/pre>\n<h2>Step 7 \u2014 Create Auth Controller<\/h2>\n<pre><code>php artisan make:controller Api\/AuthController<\/code><\/pre>\n<pre><code>&lt;?php\r\n\r\nnamespace App\\Http\\Controllers\\Api;\r\n\r\nuse App\\Http\\Controllers\\Controller;\r\nuse App\\Models\\User;\r\nuse Illuminate\\Http\\Request;\r\nuse Illuminate\\Http\\JsonResponse;\r\nuse Illuminate\\Support\\Facades\\Hash;\r\nuse Illuminate\\Validation\\ValidationException;\r\n\r\nclass AuthController extends Controller\r\n{\r\n    public function register(Request $request): JsonResponse\r\n    {\r\n        $validated = $request-&gt;validate([\r\n            'name'     =&gt; 'required|string|max:255',\r\n            'email'    =&gt; 'required|email|unique:users',\r\n            'password' =&gt; 'required|min:8|confirmed',\r\n        ]);\r\n\r\n        $user = User::create([\r\n            'name'     =&gt; $validated['name'],\r\n            'email'    =&gt; $validated['email'],\r\n            'password' =&gt; Hash::make($validated['password']),\r\n        ]);\r\n\r\n        $token = $user-&gt;createToken('api-token')-&gt;plainTextToken;\r\n\r\n        return response()-&gt;json(['user' =&gt; $user, 'token' =&gt; $token], 201);\r\n    }\r\n\r\n    public function login(Request $request): JsonResponse\r\n    {\r\n        $request-&gt;validate([\r\n            'email'    =&gt; 'required|email',\r\n            'password' =&gt; 'required',\r\n        ]);\r\n\r\n        $user = User::where('email', $request-&gt;email)-&gt;first();\r\n\r\n        if (!$user || !Hash::check($request-&gt;password, $user-&gt;password)) {\r\n            throw ValidationException::withMessages([\r\n                'email' =&gt; ['The provided credentials are incorrect.'],\r\n            ]);\r\n        }\r\n\r\n        $token = $user-&gt;createToken('api-token')-&gt;plainTextToken;\r\n        return response()-&gt;json(['user' =&gt; $user, 'token' =&gt; $token]);\r\n    }\r\n\r\n    public function logout(Request $request): JsonResponse\r\n    {\r\n        $request-&gt;user()-&gt;currentAccessToken()-&gt;delete();\r\n        return response()-&gt;json(['message' =&gt; 'Logged out successfully']);\r\n    }\r\n}<\/code><\/pre>\n<h2>Step 8 \u2014 Define API Routes<\/h2>\n<pre><code>&lt;?php\r\n\r\nuse App\\Http\\Controllers\\Api\\AuthController;\r\nuse App\\Http\\Controllers\\Api\\TaskController;\r\nuse Illuminate\\Support\\Facades\\Route;\r\n\r\nRoute::post('\/register', [AuthController::class, 'register']);\r\nRoute::post('\/login',    [AuthController::class, 'login']);\r\n\r\nRoute::middleware('auth:sanctum')-&gt;group(function () {\r\n    Route::post('\/logout', [AuthController::class, 'logout']);\r\n    Route::apiResource('tasks', TaskController::class);\r\n});<\/code><\/pre>\n<h2>Step 9 \u2014 Test the API<\/h2>\n<pre><code>php artisan serve<\/code><\/pre>\n<p><strong>Register a user:<\/strong><\/p>\n<pre><code>POST http:\/\/localhost:8000\/api\/register\r\nContent-Type: application\/json\r\n\r\n{\r\n  \"name\": \"Atul\",\r\n  \"email\": \"atul@example.com\",\r\n  \"password\": \"password123\",\r\n  \"password_confirmation\": \"password123\"\r\n}<\/code><\/pre>\n<p><strong>Create a task:<\/strong><\/p>\n<pre><code>POST http:\/\/localhost:8000\/api\/tasks\r\nAuthorization: Bearer YOUR_TOKEN_HERE\r\nContent-Type: application\/json\r\n\r\n{\r\n  \"title\": \"Build the API\",\r\n  \"description\": \"Complete the Laravel REST API guide\",\r\n  \"status\": \"in_progress\",\r\n  \"due_date\": \"2026-06-30\"\r\n}<\/code><\/pre>\n<h2>API Endpoints Summary<\/h2>\n<table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th>Endpoint<\/th>\n<th>Description<\/th>\n<th>Auth<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>POST<\/td>\n<td>\/api\/register<\/td>\n<td>Register new user<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>POST<\/td>\n<td>\/api\/login<\/td>\n<td>Login user<\/td>\n<td>No<\/td>\n<\/tr>\n<tr>\n<td>POST<\/td>\n<td>\/api\/logout<\/td>\n<td>Logout user<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>GET<\/td>\n<td>\/api\/tasks<\/td>\n<td>List all tasks<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>POST<\/td>\n<td>\/api\/tasks<\/td>\n<td>Create task<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>GET<\/td>\n<td>\/api\/tasks\/{id}<\/td>\n<td>Get single task<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>PUT<\/td>\n<td>\/api\/tasks\/{id}<\/td>\n<td>Update task<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>DELETE<\/td>\n<td>\/api\/tasks\/{id}<\/td>\n<td>Delete task<\/td>\n<td>Yes<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Best Practices for Production<\/h2>\n<p><strong>Version your API:<\/strong> Use <code>\/api\/v1\/<\/code> prefix so you can release v2 without breaking existing clients.<\/p>\n<p><strong>Rate limiting:<\/strong><\/p>\n<pre><code>Route::middleware(['auth:sanctum', 'throttle:60,1'])-&gt;group(function () {\r\n    \/\/ your routes\r\n});<\/code><\/pre>\n<p><strong>Cache repeated responses:<\/strong><\/p>\n<pre><code>$tasks = cache()-&gt;remember('user_tasks_' . $request-&gt;user()-&gt;id, 300, function () use ($request) {\r\n    return $request-&gt;user()-&gt;tasks()-&gt;latest()-&gt;paginate(10);\r\n});<\/code><\/pre>\n<p>If you need a custom REST API built for your business \u2014 whether it&#8217;s a mobile app backend, third-party integration, or internal tool \u2014 <a href=\"https:\/\/softcrony.com\/contact\/\">our team at Softcrony is happy to help<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Laravel makes building REST APIs surprisingly straightforward. In this guide we&#8217;ll build a complete, production-ready API from scratch using Laravel 12 \u2014 covering everything from setup to authentication. By the end you&#8217;ll have a fully working API that follows REST best practices and is ready to connect to any frontend \u2014 React, Vue, mobile app, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":28,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[34,19,33,30,32,18],"class_list":["post-27","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-backend","tag-laravel","tag-laravel-12","tag-php","tag-rest-api","tag-web-development"],"_links":{"self":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/27","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/comments?post=27"}],"version-history":[{"count":1,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/27\/revisions"}],"predecessor-version":[{"id":29,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/27\/revisions\/29"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/media\/28"}],"wp:attachment":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/media?parent=27"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/categories?post=27"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/tags?post=27"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}