API yang tidak terdokumentasi adalah API rahasia: tidak ada calon pengguna yang tahu endpoint apa saja yang tersedia, parameter apa yang diterima, dan bentuk apa yang dikembalikan. Seluruh keputusan pada Section ini — penamaan URL, status code, bentuk error, aturan validasi — baru bernilai penuh setelah dituliskan menjadi dokumentasi yang bisa dibaca orang lain. Untuk itu industri berkumpul pada satu standar: OpenAPI Specification, format formal untuk mendeskripsikan REST API. Pada Chapter penutup Section ini kita mempelajari anatomi dokumen OpenAPI, schema yang dipakai ulang, dan cara spesifikasi yang sama melayani manusia lewat antarmuka interaktif sekaligus mesin lewat perangkat otomatis.

Apa itu OpenAPI #

OpenAPI Specification adalah format baku — dikelola oleh OpenAPI Initiative — untuk mendeskripsikan seluruh permukaan sebuah REST API: jalur, method, parameter, request body, response, hingga skema datanya. Satu dokumen OpenAPI bisa ditulis dalam JSON atau YAML; isinya sama, hanya bentuk penulisan yang berbeda. Nama Swagger sering muncul bersandingan karena proyek ini bermula dari Swagger sebelum disumbangkan menjadi standar terbuka; hari ini Swagger lebih merujuk pada perangkat pendampingnya seperti Swagger UI, yang akan kita bahas di bawah.

Dokumen ini bukan sekadar teks penjelasan. Karena bentuknya baku dan terstruktur, dokumen OpenAPI bisa dibaca mesin: peramban dokumentasi merendernya menjadi halaman interaktif, generator membangkitkan client SDK dalam berbagai bahasa, dan alat uji memverifikasi bahwa response server sungguhan cocok dengan janjinya.

Anatomi Dokumen OpenAPI #

Empat bagian utama dokumen OpenAPI: openapi mencantumkan versi spesifikasi yang dipakai; info memuat identitas API (judul, versi, deskripsi); servers mendaftarkan alamat dasar; dan paths mendeskripsikan setiap endpoint beserta method, parameter, dan responsenya. Berikut potongan dokumen untuk API tugas kita, ditulis dalam YAML:

yaml openapi.yaml
openapi: 3.1.0
info:
  title: API Manajemen Tugas
  version: 1.0.0
  description: API untuk mengelola daftar tugas beserta komentarnya.
servers:
  - url: /v1
paths:
  /tugas:
    get:
      summary: Daftar seluruh tugas
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [belum, selesai]
      responses:
        "200":
          description: Daftar tugas
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Tugas"
    post:
      summary: Buat tugas baru
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TugasBaru"
      responses:
        "201":
          description: Tugas berhasil dibuat
  /tugas/{id}:
    get:
      summary: Detail satu tugas
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Detail tugas
        "404":
          description: Tugas tidak ditemukan
components:
  schemas:
    Tugas:
      type: object
      required: [id, title, status]
      properties:
        id:
          type: integer
        title:
          type: string
          minLength: 3
          maxLength: 80
        status:
          type: string
          enum: [belum, selesai]
    TugasBaru:
      type: object
      required: [title]
      properties:
        title:
          type: string
        status:
          type: string
          enum: [belum, selesai]
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [kode, pesan]
          properties:
            kode:
              type: string
            pesan:
              type: string

Perhatikan bagaimana potongan di atas adalah blueprint dari Chapter Merancang Endpoint REST yang dinyatakan secara formal: parameter status dengan nilai enum yang sama seperti STATUS_VALID kita, minLength dan maxLength yang mengikuti aturan validasi Chapter Validasi Input, serta schema Error yang menegaskan bentuk error seragam dari Chapter Status Code dan Penanganan Error.

Schema yang Dipakai Ulang #

Bagian components.schemas mendefinisikan bentuk data sekali, lalu bagian lain merujuknya lewat $ref. Schema Tugas menjelaskan representasi lengkap (id, title, status), sedangkan TugasBaru hanya memuat field yang dikirim client saat membuat tugas — id memang dihasilkan server, bukan dikirim client. Pola ini persis semangat fungsi helper kita: satu sumber kebenaran, dirujuk dari banyak tempat. Saat aturan berubah — misalnya batas panjang judul — kita mengubah satu schema dan seluruh dokumentasi yang merujuknya ikut benar.

Dari Spesifikasi ke Antarmuka #

Dokumen OpenAPI yang berdiri sendiri hanyalah berkas data. Nilainya melipat gandakan saat dirender: Swagger UI dan peramban sejenis mengubah spesifikasi menjadi halaman dokumentasi interaktif — daftar endpoint yang bisa diklik, contoh request, bahkan tombol try it out yang benar-benar mengirim permintaan ke server. Materi lanjutan tentang praktik menulis OpenAPI tersedia di situs belajar OpenAPI . Karena spesifikasinya baku, tim juga bisa memilih dua arah kerja: spec-first, menulis dokumen lebih dulu lalu membangkitkan kerangka server dan SDK darinya, atau code-first, menulis kode lebih dulu lalu membangkitkan dokumen dari anotasi. API kecil cocok dengan salah satunya; API besar biasanya memakai keduanya dengan dokumentasi sebagai kontrak yang disepakati dua arah.

Menyajikan openapi.json #

Kebiasaan yang berkembang di industri: server menyajikan spesifikasinya sendiri di jalur /openapi.json. Demo berikut melakukan hal itu — spesifikasi tersimpan sebagai object JavaScript, disajikan sebagai JSON, lalu dibaca kembali secara programatik untuk membuktikan bahwa mesin pun bisa “membaca dokumentasi”:

js
import http from 'node:http';

const host = '127.0.0.1';
const tugas = [
  { id: 1, title: 'Belajar Node.js', status: 'selesai' },
  { id: 2, title: 'Menulis dokumentasi API', status: 'belum' },
];

const spesifikasi = {
  openapi: '3.1.0',
  info: {
    title: 'API Manajemen Tugas',
    version: '1.0.0',
    description: 'API untuk mengelola daftar tugas.',
  },
  servers: [{ url: '/v1' }],
  paths: {
    '/tugas': {
      get: {
        summary: 'Daftar seluruh tugas',
        parameters: [
          {
            name: 'status',
            in: 'query',
            schema: { type: 'string', enum: ['belum', 'selesai'] },
          },
        ],
        responses: {
          200: { description: 'Daftar tugas' },
        },
      },
      post: {
        summary: 'Buat tugas baru',
        requestBody: {
          required: true,
          content: {
            'application/json': { schema: { $ref: '#/components/schemas/TugasBaru' } },
          },
        },
        responses: {
          201: { description: 'Tugas berhasil dibuat' },
        },
      },
    },
    '/tugas/{id}': {
      get: {
        summary: 'Detail satu tugas',
        parameters: [
          { name: 'id', in: 'path', required: true, schema: { type: 'integer' } },
        ],
        responses: {
          200: { description: 'Detail tugas' },
          404: { description: 'Tugas tidak ditemukan' },
        },
      },
    },
  },
  components: {
    schemas: {
      Tugas: {
        type: 'object',
        required: ['id', 'title', 'status'],
        properties: {
          id: { type: 'integer' },
          title: { type: 'string' },
          status: { type: 'string', enum: ['belum', 'selesai'] },
        },
      },
      TugasBaru: {
        type: 'object',
        required: ['title'],
        properties: {
          title: { type: 'string' },
          status: { type: 'string', enum: ['belum', 'selesai'] },
        },
      },
    },
  },
};

function kirimJson(res, kode, data) {
  res.writeHead(kode, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(data));
}

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://${host}`);

  if (req.method === 'GET' && url.pathname === '/openapi.json') {
    return kirimJson(res, 200, spesifikasi);
  }
  if (req.method === 'GET' && url.pathname === '/v1/tugas') {
    return kirimJson(res, 200, tugas);
  }

  return kirimJson(res, 404, {
    error: { kode: 'TIDAK_ADA', pesan: 'Endpoint tidak ditemukan' },
  });
});

server.listen(0, host, async () => {
  const port = server.address().port;
  const base = `http://${host}:${port}`;

  const spec = await fetch(`${base}/openapi.json`);
  const isi = await spec.json();
  console.log(`GET /openapi.json -> ${spec.status}`, `${isi.info.title} versi ${isi.info.version}`);
  console.log('jalur terdaftar:', Object.keys(isi.paths).join(', '));

  const daftar = await fetch(`${base}/v1/tugas`);
  const hasil = await daftar.json();
  console.log(`GET /v1/tugas -> ${daftar.status}`, `(${hasil.length} tugas)`);

  server.close();
  server.closeAllConnections();
});

Output yang Dihasilkan:

text
GET /openapi.json -> 200 API Manajemen Tugas versi 1.0.0
jalur terdaftar: /tugas, /tugas/{id}
GET /v1/tugas -> 200 (2 tugas)

Mari kita telaah bagian-bagiannya:

  1. Baris 9-79 (object spesifikasi): Struktur OpenAPI dinyatakan sebagai object JavaScript biasa — openapi, info, servers, paths, components — persis anatomi pada potongan YAML di atas. Field servers memakai URL relatif /v1, hal yang sah dalam OpenAPI; pada API production bagian ini biasanya berisi alamat absolut.
  2. Baris 17-56 (paths): Setiap jalur memuat method sebagai key — get, post — lengkap dengan summary, parameters, dan responses. Parameter query status dan parameter path id dibedakan lewat field in, dan setiap response menyertakan deskripsi singkat seperti pada blueprint kita.
  3. Baris 37 ($ref): Request body POST /tugas merujuk schema TugasBaru lewat referensi #/components/schemas/TugasBaru — satu titik definisi untuk banyak pemakaian.
  4. Baris 58-78 (components.schemas): Dua schema didefinisikan di sini: Tugas untuk representasi lengkap dan TugasBaru untuk input pembuatan. Daftar required dan enum mencerminkan aturan validasi yang sudah kita terapkan di handler.
  5. Baris 89-94 (dua rute): Server melayani /openapi.json seperti endpoint biasa — spesifikasi menjadi bagian dari API itu sendiri, siap dirender Swagger UI atau dipindai alat otomatis.
  6. Baris 105-112 (client membaca spesifikasi): fetch mengambil dokumen, isi.info.title dan Object.keys(isi.paths) membuktikan spesifikasi terstruktur — pengambilan nilai tertentu semudah membaca property object. Inilah fondasi bagi generator SDK dan pengujian kontrak: keduanya membaca bagian yang sama persis.

Section REST API kini tuntas: kita telah berjalan dari konsep resource dan statelessness, merancang blueprint, membangun CRUD, menstandarkan error, memvalidasi input, membagi halaman, mengelola versi, hingga mendokumentasikan semuanya dengan OpenAPI — semuanya di atas node:http murni. Kerangka routing dan helper yang kita tulis ulang di setiap chapter itulah yang diselesaikan framework seperti Express secara rapi; Section Framework dan Middleware memperkenalkannya, lalu Proyek REST API Manajemen Tugas mempersatukan seluruh materi ini menjadi satu aplikasi utuh.