Skip to content

Laravel Server Proxy

For PHP idioms see ../integrations/php.md.

Route

php
// routes/api.php
use App\Http\Controllers\TileController;
Route::get('/tiles/{z}/{x}/{y}.pbf', [TileController::class, 'show'])
    ->where(['z' => '[0-9]+', 'x' => '[0-9]+', 'y' => '[0-9]+']);

Controller

php
<?php
// app/Http/Controllers/TileController.php
namespace App\Http\Controllers;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;

class TileController extends Controller
{
    public function show(int $z, int $x, int $y): Response
    {
        $key    = config('services.mfd.srv_key');
        $secret = config('services.mfd.srv_secret');
        $path   = "/tiles/$z/$x/$y.pbf";
        $ts     = (string) time();
        $sig    = hash_hmac('sha256', "GET\n$path\n$ts", $secret);

        try {
            $up = Http::timeout(10)->withHeaders([
                'Authorization'   => "Bearer $key",
                'X-MFD-Timestamp' => $ts,
                'X-MFD-Signature' => $sig,
            ])->get("https://tiles.mapsfordevs.com$path");
        } catch (\Throwable $e) {
            Log::error('[mfd] tile fetch failed', ['err' => $e->getMessage()]);
            return response('', 502);
        }

        return response($up->body(), $up->status(), [
            'Content-Type'  => 'application/x-protobuf',
            'Cache-Control' => 'public, max-age=86400',
        ]);
    }
}

Config

php
// config/services.php
return [
    'mfd' => [
        'srv_key'    => env('MFD_SRV_KEY'),
        'srv_secret' => env('MFD_SRV_SECRET'),
    ],
];
bash
# .env
MFD_SRV_KEY=mfd_srv_xxx
MFD_SRV_SECRET=hex_secret_here

Cache layer (Redis)

php
public function show(int $z, int $x, int $y): Response
{
    $cacheKey = "mfd:tile:$z:$x:$y";
    $body = \Cache::remember($cacheKey, 86400, fn () => $this->fetchUpstream($z, $x, $y));

    return response($body, 200, [
        'Content-Type'  => 'application/x-protobuf',
        'Cache-Control' => 'public, max-age=86400',
    ]);
}

Octane (high-perf) gotchas

  • Octane keeps the framework boot in memory. Avoid setting global state from .env per request — use config(...) (cached at boot).
  • HTTP client connection-reuse — Laravel HTTP client uses Guzzle; in Octane it shares per-worker.

Geocoding service

php
// app/Services/MapsForDevsService.php
namespace App\Services;

use Illuminate\Support\Facades\Http;

class MapsForDevsService
{
    public function __construct(private string $apiKey) {}

    public function geocode(string $query, ?string $country = null, int $limit = 5): array
    {
        $params = ['q' => $query, 'limit' => $limit];
        if ($country) $params['country'] = $country;

        $resp = Http::withToken($this->apiKey)
            ->timeout(10)
            ->get('https://api.mapsfordevs.com/geocode/forward', $params);

        $body = $resp->json();
        if (!($body['ok'] ?? false)) throw new \RuntimeException($body['error'] ?? 'mfd error');
        return $body['items'];
    }
}

Bind in a service provider:

php
$this->app->bind(MapsForDevsService::class, fn () =>
    new MapsForDevsService(config('services.mfd.pub_key'))
);