Fermin Perdomo

Senior Full Stack Engineer | PHP | JavaScript

Simple way to create an api service with Laravel

Fermin Perdomo
September 26, 2025

1) New project + API mode

composer create-project laravel/laravel thirdparty-api
cd thirdparty-api
php artisan serve

In .env set your DB and APP_URL.


2) Auth for third parties (Sanctum personal tokens)

composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

Enable Sanctum on API routes (token guard is default in Laravel 12).

Create a token (admin-only action example):

// Somewhere in your admin controller or tinker
$user = \App\Models\User::first();
$token = $user->createToken('partner-foo')->plainTextToken;
// Give this token to the partner (store securely).

Partners send:

Authorization: Bearer <token>

3) Versioned routes + rate limit

routes/api.php

use App\Http\Controllers\Api\V1\ContactController;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Cache\RateLimiting\Limit;

RateLimiter::for('partner', fn($request) => [
    Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip()),
]);

Route::prefix('v1')
    ->middleware(['auth:sanctum','throttle:partner'])
    ->group(function () {
        Route::get('contacts', [ContactController::class, 'index']);
        Route::post('contacts', [ContactController::class, 'store']);
        Route::get('contacts/{contact}', [ContactController::class, 'show']);
        Route::patch('contacts/{contact}', [ContactController::class, 'update']);
        Route::delete('contacts/{contact}', [ContactController::class, 'destroy']);
    });

4) Model + migration

php artisan make:model Contact -m

database/migrations/xxxx_create_contacts_table.php

public function up(): void
{
    Schema::create('contacts', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->string('phone')->nullable();
        $table->timestamps();
    });
}

php artisan migrate

5) Requests (validation) + Resource (serialization)

php artisan make:request Api/V1/StoreContactRequest
php artisan make:request Api/V1/UpdateContactRequest
php artisan make:resource Api/V1/ContactResource

app/Http/Requests/Api/V1/StoreContactRequest.php

class StoreContactRequest extends FormRequest
{
    public function authorize(): bool { return true; }

    public function rules(): array
    {
        return [
            'name'  => ['required','string','max:120'],
            'email' => ['required','email','max:255','unique:contacts,email'],
            'phone' => ['nullable','string','max:40'],
        ];
    }
}

app/Http/Requests/Api/V1/UpdateContactRequest.php

class UpdateContactRequest extends FormRequest
{
    public function authorize(): bool { return true; }

    public function rules(): array
    {
        $id = $this->route('contact')->id;
        return [
            'name'  => ['sometimes','required','string','max:120'],
            'email' => ['sometimes','required','email','max:255',"unique:contacts,email,{$id}"],
            'phone' => ['nullable','string','max:40'],
        ];
    }
}

app/Http/Resources/Api/V1/ContactResource.php

class ContactResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id'    => $this->id,
            'name'  => $this->name,
            'email' => $this->email,
            'phone' => $this->phone,
            'created_at' => $this->created_at?->toIso8601String(),
            'updated_at' => $this->updated_at?->toIso8601String(),
        ];
    }
}

6) Controller (clean CRUD, pagination, filters)

php artisan make:controller Api/V1/ContactController

app/Http/Controllers/Api/V1/ContactController.php

namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1\StoreContactRequest;
use App\Http\Requests\Api\V1\UpdateContactRequest;
use App\Http\Resources\Api\V1\ContactResource;
use App\Models\Contact;
use Illuminate\Http\Request;

class ContactController extends Controller
{
    public function index(Request $request)
    {
        $query = Contact::query();

        // Simple filtering
        if ($email = $request->query('email')) {
            $query->where('email', $email);
        }
        if ($search = $request->query('q')) {
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%")
                  ->orWhere('email', 'like', "%{$search}%");
            });
        }

        $contacts = $query->orderByDesc('id')->paginate(
            perPage: min((int) $request->query('per_page', 20), 100)
        );

        return ContactResource::collection($contacts)
            ->additional(['meta' => ['version' => '1.0']]);
    }

    public function store(StoreContactRequest $request)
    {
        $contact = Contact::create($request->validated());
        return (new ContactResource($contact))
            ->response()
            ->setStatusCode(201);
    }

    public function show(Contact $contact)
    {
        return new ContactResource($contact);
    }

    public function update(UpdateContactRequest $request, Contact $contact)
    {
        $contact->update($request->validated());
        return new ContactResource($contact);
    }

    public function destroy(Contact $contact)
    {
        $contact->delete();
        return response()->noContent();
    }
}

7) Consistent error shape (optional, but recommended)

In app/Exceptions/Handler.php, ensure validation errors and other exceptions return JSON for API routes (Laravel 12 already does for JSON requests). You can standardize further with a small formatter if needed.

8) CORS (so partners can call from browsers)

Install & configure if you need custom rules:

  • Edit config/cors.php (allowed_origins, paths => ['api/*']).
  • In .env, set SESSION_DRIVER=cookie is fine; Sanctum personal tokens work from servers and browsers.

9) API keys alternative (HMAC or header key)

If you don’t want user accounts, add a lightweight API-Key middleware:

php artisan make:middleware ApiKeyGuard

app/Http/Middleware/ApiKeyGuard.php

class ApiKeyGuard
{
    public function handle($request, Closure $next)
    {
        $provided = $request->header('X-Api-Key');
        $valid = config('services.partners.keys', []); // e.g., ['foo' => 'abc123', 'bar' => 'xyz456']

        if (! $provided || ! in_array($provided, $valid, true)) {
            return response()->json(['message' => 'Unauthorized'], 401);
        }

        return $next($request);
    }
}

Register in app/Http/Kernel.php, then swap auth:sanctum for apikey in routes if desired.

10) Docs (OpenAPI)

composer require "darkaonline/l5-swagger"
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"

Add annotations to controllers/requests, then:

php artisan l5-swagger:generate

Docs available at /api/documentation (configurable). Share with partners.

11) Testing (Pest)

composer require pestphp/pest --dev
php artisan pest:install
php artisan make:test Api/V1/ContactApiTest

tests/Feature/Api/V1/ContactApiTest.php

it('lists contacts', function () {
    $user = \App\Models\User::factory()->create();
    \App\Models\Contact::factory()->count(3)->create();

    $this->actingAs($user, 'sanctum')
        ->getJson('/api/v1/contacts')
        ->assertOk()
        ->assertJsonStructure(['data', 'links', 'meta']);
});

12) Usage examples

List (with search & pagination):

curl -H "Authorization: Bearer <TOKEN>" \
  "https://your-app.test/api/v1/contacts?q=fermin&per_page=10"

Create:

curl -X POST -H "Authorization: Bearer <TOKEN>" -H "Content-Type: application/json" \
  -d '{"name":"Hamlet","email":"[email protected]","phone":"+1-809-555-0000"}' \
  https://your-app.test/api/v1/contacts

Extras you can add later

  • Webhooks (signed with HMAC header)
  • Request signing (HMAC of body + timestamp to prevent replay)
  • Soft deletes with SoftDeletes
  • Cursor pagination for very large datasets
  • E-tags/If-None-Match for caching
  • Feature flags per partner (limits, fields)


Reactions

Loading reactions...
Log in to react to this post.

Comments

Please login to leave a comment.

Great Tools for Developers

Git tower

Git tower

A powerful Git client for Mac and Windows that simplifies version control.

Get Started - It's Free
Visit Tool
Mailcoach's

Mailcoach's

Self-hosted email marketing platform for sending newsletters and automated emails.

Start free
Visit Tool
Uptimia

Uptimia

Website monitoring and performance testing tool to ensure your site is always up and running.

Start free
Visit Tool

Newsletter