← Back to list

Scramble Docs API in Deployment

Salah satu masalah klasik saat membangun REST API dengan Laravel adalah dokumentasi API yang cepat basi. Kita nulis dokumentasi manual di…

Sindu Aditya · 2026-07-18 14:55 · 0 claps · 5.7 min read
#technical-documentation #api-documentation
Open on Medium ↗

Scramble Docs API in Deployment

Salah satu masalah klasik saat membangun REST API dengan Laravel adalah dokumentasi API yang cepat basi. Kita nulis dokumentasi manual di Postman atau Notion, lalu lupa update setiap kali ada endpoint baru atau field request berubah. Ujung-ujungnya, dokumentasi dan kode berjalan sendiri-sendiri.

Scramble menyelesaikan masalah ini dengan cara yang elegan: dokumentasi API di-generate otomatis dari kode controller, Form Request, API Resource tanpa perlu anotasi manual macam-macam. Tapi ada satu hal yang jarang dibahas:

bagaimana kalau dokumentasi ini di-deploy ke server, tapi kita tidak mau semua orang bisa mengintip endpoint API kita?

Di tutorial ini, kita akan setup Scramble dengan 3 skenario akses berbeda:

  • Local : bebas akses, tanpa login, buat kebutuhan development sehari-hari.
  • Staging : dokumentasi tersedia tapi dikunci di balik halaman login sederhana, buat tim QA/internal yang butuh akses.
  • Production : dokumentasi API benar-benar diblokir, tidak ada jalur masuk sama sekali, demi keamanan.

Prasyarat

  1. Project Laravel 11/12 yang sudah berjalan.
  2. Composer & PHP 8.2+.
  3. API route sudah ada minimal satu endpoint (misalnya routes/api.php).

Step 1 — Install Scramble

composer require dedoc/scramble

Scramble otomatis auto-discover service provider-nya, jadi tidak perlu registrasi manual di config/app.php.

Coba akses langsung setelah instalasi:

http://localhost:8000/docs/api

Kalau muncul halaman dokumentasi berisi daftar endpoint dari routes/api.php, instalasi sudah berhasil. Secara default, Scramble scan semua route yang prefix-nya api dan tidak butuh konfigurasi tambahan untuk bisa jalan di local.v

Step 2 — Publish Config Scramble

php artisan vendor:publish --provider="Dedoc\Scramble\ScrambleServiceProvider"

Command ini melakukan dua hal:

  1. Copy config/scramble.php ke project — di sini kita bisa atur api_path, judul dokumentasi, tema UI, dan seterusnya.
  2. Copy folder view Scramble ke resources/views/vendor/scramble/ — inilah file yang nanti akan kita modifikasi supaya bisa menyisipkan elemen custom (misalnya tombol logout) ke halaman dokumentasi.

Kenapa view-nya bisa di-override cukup dengan copy file? Laravel punya konvensi resolusi view untuk package: setiap kali sebuah package memanggil loadViewsFrom($path, 'namespace') di service provider-nya, Laravel akan cek dulu apakah ada file yang sama persis di resources/views/vendor/{namespace}/... pada project kita. Kalau ada, versi kita yang dipakai. Kalau tidak ada, Laravel jatuh kembali (fallback) ke view asli di dalam folder vendor/{package}/....

Step 3 — Tambahkan Skema Auth JWT/Bearer ke Docs (opsional tapi direkomendasikan)

Kalau API kita pakai token Bearer (JWT, Sanctum token, dsb), tambahkan skema keamanan supaya tombol “Authorize” muncul di UI dokumentasi dan endpoint yang butuh auth ditandai dengan jelas.

Buka app/Providers/AppServiceProvider.php:

use Dedoc\Scramble\Scramble;
use Dedoc\Scramble\Support\Generator\OpenApi;
use Dedoc\Scramble\Support\Generator\SecurityScheme;

public function boot(): void
{
    Scramble::extendOpenApi(function (OpenApi $openApi) {
        $openApi->secure(
            SecurityScheme::http('bearer', 'JWT')
        );
    });
}

Sekarang di /docs/api, akan muncul tombol Authorize di pojok atas yang memungkinkan kita paste token dan langsung test endpoint dari UI (fitur "Try It").

Step 4 — Buat Sistem Gate Berbasis Environment

Ini bagian inti dari tutorial ini. Kita akan pakai Laravel Gate untuk menentukan siapa boleh lihat dokumentasi, berdasarkan environment aplikasi (APP_ENV).

Masih di app/Providers/AppServiceProvider.php, tambahkan:

use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    // ... kode Scramble::extendOpenApi di atas tetap ada ...

    Gate::define('viewApiDocs', function ($user = null) {
        // Local: bebas akses, tanpa syarat apa pun
        if (app()->environment('local')) {
            return true;
        }

        // Staging: harus login lewat halaman docs terlebih dahulu
        if (app()->environment('staging')) {
            return session('docs_authenticated') === true;
        }

        // Production (dan environment lain di luar local/staging):
        // tidak ada akses sama sekali, tanpa pengecualian
        return false;
    });
}

Poin penting di sini: untuk production, kita sengaja tidak membuat jalur bypass apa pun (misalnya lewat role admin atau token rahasia). Prinsipnya sederhana — dokumentasi API adalah peta lengkap struktur endpoint, request, dan response aplikasi kita. Di production, ini adalah informasi sensitif yang sebaiknya tidak bisa diakses dari luar dengan cara apa pun lewat jalur ini.

Step 5 — Middleware yang Mengeksekusi Gate

Buat middleware baru:

php artisan make:middleware ScrambleDocsAccess

Isi app/Http/Middleware/ScrambleDocsAccess.php:

<?php

namespace App\Http\Middleware;

use Illuminate\Support\Facades\Gate;

class ScrambleDocsAccess
{
    public function handle($request, \Closure $next)
    {
        if (app()->environment('local')) {
            return $next($request);
        }

        if (Gate::allows('viewApiDocs')) {
            return $next($request);
        }

        return redirect()->route('docs.login');

Lalu daftarkan middleware ini di config/scramble.php:

'middleware' => [
    'web',
    \App\Http\Middleware\ScrambleDocsAccess::class,
],

Middleware web wajib ada di urutan pertama karena kita akan pakai session untuk menyimpan status login — dan session hanya tersedia lewat middleware group web, bukan api.

Step 6 — Buat Halaman Login Sederhana untuk Staging

Kita tidak perlu sistem login penuh dengan tabel user terpisah untuk ini — cukup satu pasang kredensial “shared secret” yang disimpan di environment variable, khusus untuk tim internal yang butuh akses ke staging.

6.1 — Buat config kredensial

Buat file config/docs-auth.php:

<?php

return [
    /*
    |--------------------------------------------------------------------------
    | API Documentation Access Credentials
    |--------------------------------------------------------------------------
    |
    | Kredensial untuk mengakses halaman dokumentasi API di environment
    | staging. Nilai WAJIB diisi lewat .env, jangan pernah hardcode
    | langsung di sini atau commit ke repository.
    |
    */

    'username' => env('DOCS_USERNAME'),
    'password' => env('DOCS_PASSWORD'),
]

6.2 — Buat controller login

php artisan make:controller Web/DocsAuthController

Isi app/Http/Controllers/Web/DocsAuthController.php:

<?php

namespace App\Http\Controllers\Web;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class DocsAuthController extends Controller
{
    public function show()
    {
        if (session('docs_authenticated')) {
            return redirect()->intended('/docs/api');
        }

        return view('docs.login');
    }

    public function login(Request $request)
    {
        $request->validate([
            'username' => 'required|string',
            'password' => 'required|string',
        ]);

        $validUser = config('docs-auth.username');
        $validPass = config('docs-auth.password');

        if (
            is_string($validUser) && is_string($validPass) &&
            $request->username === $validUser &&
            hash_equals($validPass, $request->password)
        ) {
            session(['docs_authenticated' => true]);
            return redirect()->intended('/docs/api');
        }

        return back()
            ->withInput(['username' => $request->username])
            ->withErrors(['message' => 'Invalid credentials. Please try again.']);
    }

    public function logout()
    {
        session()->forget('docs_authenticated');
        return redirect()->route('docs.login');
    }
}

Dua hal yang perlu digarisbawahi:

  • Pakai hash_equals(), bukan ===, untuk membandingkan password. Ini adalah timing-safe comparison — mencegah timing attack, di mana penyerang bisa menebak karakter password satu per satu dengan mengukur perbedaan waktu respons.
  • is_string($validUser) dicek dulu sebelum komparasi. Ini mencegah kasus di mana DOCS_USERNAME/DOCS_PASSWORD lupa di-set di .env (jadi bernilai null), yang kalau tidak dicek bisa berpotensi lolos validasi secara tidak sengaja tergantung logika pembanding yang dipakai.

6.3 — Buat view login

Buat resources/views/docs/login.blade.php dengan form sederhana:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>API Docs Login</title>
</head>
<body>
    <h1>API Documentation Access</h1>

    @if ($errors->any())
        <p style="color: red;">{{ $errors->first('message') }}</p>
    @endif

    <form method="POST" action="{{ route('docs.login.post') }}">
        @csrf
        <label>
            Username
            <input type="text" name="username" value="{{ old('username') }}" required>
        </label>
        <label>
            Password
            <input type="password" name="password" required>
        </label>
        <button type="submit">Login</button>
    </form>
</body>
</html>

6.4 — Daftarkan route

Di routes/web.php:

use App\Http\Controllers\Web\DocsAuthController;

Route::get('/docs/login', [DocsAuthController::class, 'show'])->name('docs.login');
Route::post('/docs/login', [DocsAuthController::class, 'login'])->name('docs.login.post');
Route::post('/docs/logout', [DocsAuthController::class, 'logout'])->name('docs.logout');

Step 7 — Tambahkan Tombol Logout di Halaman Dokumentasi

Sekarang kita edit view Scramble yang sudah di-publish di Step 2. Buka resources/views/vendor/scramble/docs.blade.php, lalu sisipkan blok berikut tepat setelah tag <body>:

@if(session('docs_authenticated'))
    <div style="position: absolute; top: 12px; right: 24px; z-index: 9999;">
        <form method="POST" action="{{ route('docs.logout') }}">
            @csrf
            <button type="submit" style="
                background: #dc2626;
                color: white;
                border: none;
                padding: 8px 16px;
                border-radius: 6px;
                cursor: pointer;
            ">
                Logout
            </button>
        </form>
    </div>
@endif

Tombol ini hanya muncul kalau session('docs_authenticated') bernilai true — artinya di local (yang tidak pernah set session ini), tombol tidak akan pernah muncul, dan itu memang perilaku yang diharapkan karena di local memang tidak ada konsep "login" untuk dokumentasi.

Step 8 — Konfigurasi Environment

Di file .env masing-masing environment, atur sebagai berikut.

Local (.env development):

APP_ENV=local

Tidak perlu variabel tambahan apa pun.

Staging (.env di server staging):

APP_ENV=staging
DOCS_USERNAME=ganti_dengan_username_rahasia
DOCS_PASSWORD=ganti_dengan_password_yang_kuat

Gunakan password yang panjang dan acak — simpan lewat secret manager (misalnya GitHub Actions secrets, AWS Secrets Manager, atau tool serupa) saat deploy, jangan pernah commit nilai asli ke repository.

Production (.env di server production):

APP_ENV=production

Tidak perlu DOCS_USERNAME/DOCS_PASSWORD — bahkan kalau di-set pun tidak akan pernah dipakai, karena Gate di Step 4 sudah sengaja return false untuk semua environment selain local dan staging.

Setelah mengubah .env di server mana pun, jangan lupa jalankan:

php artisan config:clear
php artisan config:cache

Laravel meng-cache file config saat deploy, jadi perubahan .env tidak akan terbaca sampai cache di-refresh.

Step 9 — Uji Coba Tiap Skenario

Local

php artisan serve

Buka http://localhost:8000/docs/api → dokumentasi langsung tampil tanpa redirect apa pun.

Staging

  1. Buka https://staging.contoh-domain.com/docs/api → otomatis redirect ke /docs/login.
  2. Masukkan kredensial dari DOCS_USERNAME/DOCS_PASSWORD.
  3. Berhasil login → redirect balik ke /docs/api, dokumentasi tampil lengkap dengan tombol Logout di pojok kanan atas.
  4. Klik Logout → session dihapus, kembali ke halaman login.

Production

Buka https://api.contoh-domain.com/docs/api → selalu redirect ke /docs/login, dan form login pun tidak akan pernah berhasil meski kredensial yang dimasukkan benar — karena Gate viewApiDocs sudah mengembalikan false secara permanen untuk environment ini, tanpa jalur pengecualian.

Ringkasan

Dengan pendekatan ini, tim development bisa bekerja cepat di local tanpa friksi, tim QA/internal tetap bisa mengakses dokumentasi API terkini di staging dengan kontrol akses minimal, dan yang paling penting — struktur endpoint API di production tidak pernah terekspos ke pihak luar lewat jalur dokumentasi.

Beberapa hal yang bisa dikembangkan lebih lanjut dari fondasi ini:

  • Ganti sistem login shared-credential dengan autentikasi berbasis user/role kalau tim sudah lebih besar.
  • Tambahkan rate limiting di route /docs/login untuk mencegah brute-force.
  • Log setiap percobaan login (berhasil maupun gagal) ke sistem audit trail.
  • Pertimbangkan menaruh dokumentasi di balik VPN internal sebagai lapisan keamanan tambahan, alih-alih hanya mengandalkan Gate di level aplikasi.

메타데이터
post_id
7e277fec53ed
slug
scramble-docs-api-in-deployment-7e277fec53ed
url
https://medium.com/@sinduaditya/scramble-docs-api-in-deployment-7e277fec53ed
canonical_url
https://medium.com/@sinduaditya/scramble-docs-api-in-deployment-7e277fec53ed
author_url
https://medium.com/@sinduaditya
status
ok
fetched_at
2026-08-04 02:16:29