Simple way to create an api service with Laravel

Fermin Perdomo Fermin Perdomo
โ€ข โ€ข schedule 5 min read

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

lock You need to be logged in to react.
Log In

Newsletter

Get new posts delivered straight to your inbox.

mail

Great Tools for Developers

Git Tower

Git Tower

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

Visit arrow_forward
Mailcoach

Mailcoach

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

Visit arrow_forward
Uptimia

Uptimia

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

Visit arrow_forward
Cloudways

Cloudways

Managed cloud hosting platform that simplifies server management for developers.

Visit arrow_forward

Comments

No comments yet. Be the first to share your thoughts.

chat_bubble Join the conversation โ€” log in to leave a comment.
Log In