aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Web Development

Laravel API Building: RESTful Endpoint Design

Laravel API building, when done right, gives you a solid foundation that both a mobile app and a single-page (SPA) front end can be built on with confidence. In this article we won't start from an empty list of endpoints; instead we'll design a real REST API around a consistent resource model, a clean JSON output and a forward-looking versioning strategy. The goal isn't an API that merely "works" — it's one that is maintainable, predictable and that clients can trust.

Thinking in resources, not actions

The essence of RESTful design is putting resources in your URLs, not actions. Instead of verb-laden addresses like /api/createPost, you name the resource as a noun and let the HTTP method express the action:

  • GET /api/posts — list
  • POST /api/posts — create
  • GET /api/posts/{id} — show one record
  • PUT/PATCH /api/posts/{id} — update
  • DELETE /api/posts/{id} — delete

Laravel supports this pattern directly. The Route::apiResource() definition generates the five routes above in a single line; unlike the classic resource in web.php, it skips the create and edit routes that show forms — because an API has no HTML forms.

Setting up routes with a resource controller

First, generate a controller with the --api flag:

php artisan make:controller Api/V1/PostController --api --model=Post

Then wire it up in routes/api.php with one line:

use App\Http\Controllers\Api\V1\PostController;

Route::apiResource('posts', PostController::class);

This automatically maps the index, store, show, update and destroy methods to the appropriate HTTP verbs. The {post} route parameter, when type-hinted, turns straight into the related Post object via Laravel's route model binding; if no record is found, Laravel returns a 404 on its own:

public function show(Post $post)
{
    return new PostResource($post);
}

Shaping JSON output with API Resources

Returning the model directly with return $post; works, but it's dangerous: every database column (internal notes, password, timestamps) leaks out as-is, and the client breaks every time the schema changes. Instead, use an API Resource class — a transformation layer where you explicitly define the JSON representation of your model.

php artisan make:resource PostResource

You then decide which fields go out, and under what names:

public function toArray($request): array
{
    return [
        'id'         => $this->id,
        'title'      => $this->title,
        'body'       => $this->body,
        'author'     => new UserResource($this->whenLoaded('user')),
        'created_at' => $this->created_at->toIso8601String(),
    ];
}

whenLoaded() includes relational data only when it has actually been eager-loaded — a practical way to avoid the N+1 query problem. To return a collection you use PostResource::collection($posts). If you also want to return metadata such as the total page count, you can pass a paginator straight into the resource in index; Laravel automatically produces the pagination links under links and meta.

Validation and error responses

To validate incoming data without bloating the controller, Form Request classes are ideal:

php artisan make:request StorePostRequest
public function rules(): array
{
    return [
        'title' => ['required', 'string', 'max:255'],
        'body'  => ['required', 'string'],
    ];
}

When you type-hint it in the controller method, Laravel validates the request automatically; on a rule violation, a client sending the Accept: application/json header receives a 422 Unprocessable Content status code along with field-level error messages. This is the basis of a consistent error contract in REST APIs: 200/201 success, 401 authentication missing, 403 not authorized, 404 not found, 422 validation error. Using these codes consistently makes client-side error handling predictable.

Versioning: future-proofing your API

Once an API is published, changing it without breaking the clients that use it becomes hard. The answer is versioning. The most common and most explicit method is putting the version in the URL path:

Route::prefix('v1')->group(function () {
    Route::apiResource('posts', \App\Http\Controllers\Api\V1\PostController::class);
});

This keeps /api/v1/posts stable; when an incompatible change is eventually needed, you open a new version with a V2 namespace and a v2 prefix while keeping the old one running for a while. Splitting controllers into version folders like App\Http\Controllers\Api\V1 keeps the codebase physically tidy too. Alternatively, you can carry the version in the Accept header (media type versioning), but the URL-based approach is more practical for both testing in a browser and tracing in logs.

Authentication and rate limiting

To protect non-public endpoints, Laravel's Sanctum package is the preferred solution for both SPA cookie-based sessions and token-based access. You group protected routes with the auth:sanctum middleware:

Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('posts', PostController::class)
        ->except(['index', 'show']);
});

Here the read endpoints stay public while the write endpoints require authentication. It's also good practice to add a rate limit with the throttle middleware to prevent abuse; Laravel's RateLimiter lets you define requests per minute on a per-user or per-IP basis.

Frequently Asked Questions

What's the difference between apiResource and a normal resource?

Route::resource() generates seven routes, two of which (create, edit) are meant to show HTML forms. Route::apiResource() skips those two and leaves only the five data-returning routes — exactly what you need in a JSON API where there is no form rendering.

Why use an API Resource instead of returning the model directly?

Because an API Resource separates your external contract from your database schema. Even if you rename a column or add a sensitive field, the outgoing JSON won't change or leak unless you choose so. This is critical for both security and backward compatibility.

Do I need versioning from the start?

For a single small client a v1 prefix isn't mandatory, but the cost of adding it is nearly zero. Setting the path as /api/v1 from day one gives you huge flexibility when a breaking change is eventually needed; adding it later requires migrating every client.

Want a solid foundation for your API? I can help you design a scalable Laravel REST API — from resource controllers to versioning and authentication. Get in touch and let's plan your project together.

Devamı için