Schema & Type System — GraphQL

Schema & Type System Schema adalah jantung GraphQL. Setiap field punya type yang jelas — seperti TypeScript untuk API. Schema menjadi kontrak antara client…

Schema adalah daftar lengkap apa saja yang bisa diminta dari sebuah API GraphQL: type apa yang ada, field apa di tiap type, dan nilai seperti apa yang boleh ada di tiap field. Server menolak semua query yang gak cocok dengan schema, sebelum satu pun resolver jalan. Bayangin formulir resmi di kantor: tiap kolom punya jenis isian (angka, teks, tanggal), kolom bertanda bintang wajib diisi, dan beberapa kolom cuma boleh dicentang dari pilihan yang tersedia. Schema GraphQL persis seperti itu, dan tanda bintangnya adalah !.

🧪 Tebakan cepat: Resolver penulis gagal untuk satu dari tiga buku, jadi author-nya null. Schema-nya books: [Book!]! dan author: Author!. Berapa buku yang sampai ke client? Jawabannya ada di bagian "List dan tanda seru".

Schema Toko Buku

Schema ditulis dalam SDL (Schema Definition Language). Ini schema Toko Buku yang dipakai di seluruh jalur GraphQL:

type Query {
  book(id: ID!): Book
  books(status: StockStatus): [Book!]!
  author(id: ID!): Author
  authors: [Author!]!
}

type Mutation {
  createBook(input: CreateBookInput!): Book!
  updateStock(id: ID!, stock: Int!): Book
}

enum StockStatus {
  IN_STOCK
  SOLD_OUT
}

type Book {
  id: ID!
  title: String!
  price: Int!
  stock: Int!
  status: StockStatus!
  author: Author!
}

type Author {
  id: ID!
  name: String!
  books: [Book!]!
}

input CreateBookInput {
  title: String!
  authorId: ID!
  price: Int!
  stock: Int!
}

Scalar bawaan

Field paling ujung, yang gak punya field lagi di dalamnya, bernilai scalar (atau enum). GraphQL punya tepat lima scalar bawaan:

Scalar Isinya Contoh di Toko Buku
Int bilangan bulat 32-bit, dari -2.147.483.648 sampai 2.147.483.647 price: 89000
Float angka desimal (belum dipakai)
String teks title: "Laskar Pelangi"
Boolean true atau false (belum dipakai)
ID penanda unik, dikirim sebagai teks id: "2"

ID selalu dikirim sebagai string di response. Data Toko Buku menyimpan id sebagai angka 2, tapi book(id: 2) { id } menjawab "id": "2". Sebagai argument, id: 2 dan id: "2" sama-sama diterima.

Date bukan scalar bawaan. Schema yang memakai publishedAt: Date tanpa deklarasi ditolak dengan pesan Unknown type "Date".. Kalau butuh, deklarasikan custom scalar dengan scalar Date, lalu server yang menentukan cara membaca dan mengirimnya (biasanya string seperti "2026-10-11").

Tanda seru: non-null

! artinya non-null: nilainya gak boleh null.

Kalau resolver mengembalikan null untuk field ber-!, server gak mengirim null itu. Dia mencatat error Cannot return null for non-nullable field Book.author., lalu menaikkan null-nya ke tempat terdekat yang boleh null.

List dan tanda seru

[Book] artinya list berisi buku. Tanda seru bisa ada di dua tempat: di dalam kurung siku untuk tiap isinya, di luar untuk list-nya sendiri. Ini yang terjadi pada query { books { title author { name } } } kalau penulis Cantik Itu Luka (buku ketiga) jadi null:

Schema books Artinya data yang sampai ke client
[Book!]! list gak pernah null, isinya juga gak null
[Book]! list gak pernah null, isinya boleh null { "books": [buku 1, buku 2, null] }
[Book!] list boleh null, isinya gak { "books": null }
[Book] dua-duanya boleh null { "books": [buku 1, buku 2, null] }

Keempatnya juga membawa error Cannot return null for non-nullable field Book.author. dengan "path": ["books", 2, "author"]. null dari author naik ke bukunya (Book! gak boleh null), lalu ke list-nya, sampai ketemu tempat yang boleh null. Di schema Toko Buku yang asli ([Book!]!), gak ada tempat seperti itu, jadi data seluruhnya jadi null. Itu jawaban tebakannya: nol buku sampai ke client.

Kalau author dibuat boleh null (author: Author), hasilnya tiga buku, buku ketiga dengan "author": null, tanpa error sama sekali. Jadi pakai ! untuk yang memang pasti ada, dan biarkan boleh null untuk yang datang dari layanan yang bisa gagal.

Enum: pilihan yang terbatas

Pakai enum kalau sebuah field cuma boleh berisi satu dari beberapa pilihan tetap. StockStatus cuma IN_STOCK atau SOLD_OUT, dan resolver status mengisinya dari stok (stok 0 berarti SOLD_OUT):

{
  books(status: SOLD_OUT) {
    title
    stock
    status
  }
}
{ "data": { "books": [{ "title": "Bumi Manusia", "stock": 0, "status": "SOLD_OUT" }] } }

Nilai enum ditulis tanpa tanda kutip. Mesin GraphQL memeriksanya sebelum resolver jalan:

Kalau status bertype String, "HABIS" lolos validasi dan resolver yang harus mengeceknya sendiri. Enum memberi pengecekan itu gratis dari mesin GraphQL.

Input type: argument berbentuk objek

Argument yang isinya banyak field dikumpulkan dalam input type. createBook(input: CreateBookInput!) menerima satu objek berisi title, authorId, price, dan stock. Input type ditulis dengan input, bukan type, dan cuma boleh berisi scalar, enum, list, atau input type lain. Book gak bisa dipakai sebagai argument, dan CreateBookInput gak bisa jadi hasil field. Contoh pemakaiannya ada di pelajaran "Mutations".

⚠️ Jebakan yang sering ketemu

📌 Interface dan union

Interface adalah daftar field yang wajib dimiliki semua type yang mengimplementasinya. Union cuma kumpulan type, tanpa field bersama yang wajib.

interface Product {
  id: ID!
  title: String!
  price: Int!
}

type Ebook implements Product {
  id: ID!
  title: String!
  price: Int!
  fileSizeMb: Float!
}

union SearchResult = Book | Author

(Book ditulis type Book implements Product { ... } dengan field-nya yang tadi.) Di field ber-type Product, title dan price boleh diminta langsung karena pasti ada. Di SearchResult, field-nya harus diminta per type dengan inline fragment ... on Type, dan __typename memberi tahu type aslinya:

{
  search(text: "ka") {
    __typename
    ... on Book {
      title
    }
    ... on Author {
      name
    }
  }
}
{
  "data": {
    "search": [
      { "__typename": "Book", "title": "Laskar Pelangi" },
      { "__typename": "Book", "title": "Cantik Itu Luka" },
      { "__typename": "Author", "name": "Eka Kurniawan" }
    ]
  }
}

Meminta title langsung di search ditolak: Cannot query field "title" on type "SearchResult". Did you mean to use an inline fragment on "Product" or "Book"?

📌 Directive: @skip, @include, @deprecated

Directive adalah penanda berawalan @ yang mengubah cara sesuatu diproses.

📌 Introspection: schema bisa ditanya

Schema bisa ditanyakan lewat query juga. Field khusus berawalan dua garis bawah, seperti __type, disebut introspection:

{
  __type(name: "Book") {
    name
    kind
    fields {
      name
    }
  }
}

Jawabannya "name": "Book", "kind": "OBJECT", dan field id, title, price, stock, status, author. Dari sini GraphiQL dan editor membuat autocomplete. Beberapa server, misalnya Apollo Server, mematikan introspection secara default di production. Mesin di Lab belum mendukung introspection.

TL;DR: Schema, ditulis dalam SDL, adalah kontrak semua type dan field yang bisa diminta; query yang gak cocok ditolak sebelum resolver jalan. Scalar bawaan cuma lima: Int (32-bit), Float, String, Boolean, ID (dikirim sebagai string); Date bukan bawaan, jadi deklarasikan scalar Date. ! artinya non-null: di hasil itu janji server, di argument itu wajib diisi. null di field ber-! naik ke tempat terdekat yang boleh null, jadi dengan [Book!]! dan author: Author! satu penulis yang gagal bikin data seluruhnya null. Enum untuk pilihan terbatas, divalidasi otomatis oleh mesin GraphQL (string gak). Input type untuk argument berbentuk objek. Interface mewajibkan field bersama, union gak punya field bersama dan diakses dengan ... on Type. @skip melewati field kalau kondisinya true, @deprecated menandai field usang beserta alasannya.

Yang akan kamu pelajari