Pada sisi server, TypeScript berjalan di atas runtime Node.js dan memakai API bawaannya, mulai dari http untuk melayani permintaan, process untuk variabel lingkungan dan argumen command line, hingga sistem modulnya. Topik ini membahas pola server HTTP tanpa framework, pengetikan dan validasi process.env, modul konfigurasi yang memvalidasi saat startup, serta pembacaan process.argv. Setelah membacanya, layanan sisi server dapat ditulis dengan kontrak tipe yang mencakup lingkungan tempat layanan itu berjalan.
Server HTTP tanpa framework#
Modul bawaan http menyediakan createServer yang menerima fungsi callback, yaitu fungsi yang dipanggil Node.js untuk setiap permintaan masuk. Callback tersebut menerima dua parameter, yaitu permintaan bertipe IncomingMessage dan respons bertipe ServerResponse. Kedua tipe itu berasal dari @types/node, sehingga properti seperti req.url dan metode seperti res.writeHead terdokumentasi oleh compiler. Pola yang lazim adalah memisahkan keputusan rute menjadi fungsi murni agar mudah diuji, lalu mengirim hasilnya melalui respons.
// tidak-dijalankan
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
type KeputusanRute = { status: number; isi: string };
function keputusanRute(url: string | undefined): KeputusanRute {
if (url === "/sehat") {
return { status: 200, isi: JSON.stringify({ status: "ok" }) };
}
return { status: 404, isi: JSON.stringify({ status: "tidak ditemukan" }) };
}
function rute(req: IncomingMessage, res: ServerResponse): void {
const keputusan = keputusanRute(req.url);
res.writeHead(keputusan.status, { "content-type": "application/json" });
res.end(keputusan.isi);
}
const server = createServer(rute);
server.listen(3000);Fungsi keputusanRute mengambil keputusan rute berdasarkan req.url. Tipe parameternya adalah string | undefined, karena req.url memang bisa tidak terdefinisi pada beberapa jenis permintaan, dan tipe dari @types/node menyatakannya apa adanya. Fungsi rute hanya menerjemahkan keputusan itu menjadi writeHead dan end, sehingga logika dan pengiriman terpisah dengan jelas.
process.env bertipe string | undefined#
Nilai process.env dinyatakan oleh @types/node sebagai objek dengan index signature [key: string]: string | undefined, sehingga setiap pembacaan variabel lingkungan menghasilkan nilai string atau undefined. Konsekuensinya, nilai yang dibaca dari process.env tidak boleh langsung dipercaya dan sebaiknya melewati satu pintu validasi. Bentuk index signature ini memang sesuai kenyataan, karena variabel lingkungan pada dasarnya kumpulan teks bebas yang keberadaannya tidak dijanjikan, berbeda dengan parameter fungsi yang wajib dikirim pemanggilnya. Sebelum masuk ke pembahasan validasi, perlu dipahami bahwa compiler bahkan tidak mengenal nama process bila tipe Node.js belum tersedia.
// tidak-dijalankan
const port: number = process.env.PORT;
console.log(port);# memeriksa berkas tanpa tipe Node.js yang terpasang
tsc --noEmit --pretty false uji-env.ts
# memeriksa kembali dengan @types/node dari node_modules jalur ini
tsc --noEmit --pretty false --types node uji-env.tsuji-env.ts(2,22): error TS2591: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
uji-env.ts(2,7): error TS2322: Type 'string | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'.Pemeriksaan pertama berhenti pada nama process, karena tanpa @types/node compiler tidak memiliki pengetahuan apa pun tentang API Node.js. Pada sandbox jalur ini pemeriksaan dijalankan tanpa tsconfig, sehingga @types/node yang terpasang di node_modules jalur tidak dimuat otomatis, dan opsi --types node menyertainya secara eksplisit. Pada proyek dengan tsconfig, definisi itu dimuat sendiri oleh compiler, dan di titik itu keluaran pemeriksaan berpindah ke masalah yang sesungguhnya, yaitu string | undefined tidak boleh dianotasi sebagai number. Pesan kedua inilah yang paling sering ditemui saat mengetik process.env.
Fungsi pembaca env dengan validasi#
Validasi variabel lingkungan paling rapi bila dibungkus dalam satu fungsi pembaca. Fungsi bacaEnv menerima nama variabel, mengembalikan string yang dijamin terisi, dan melempar error bila nilainya tidak ada atau kosong. Tipe kembaliannya string, bukan string | undefined, karena jaminan itu dibangun di dalam fungsinya. Dengan satu pintu seperti ini, pemeriksaan cukup dilakukan di satu tempat dan seluruh pemanggil mendapat jaminan yang sama, sehingga tidak ada variabel yang lolos karena lupa diperiksa. Fungsi yang sama dapat dipanggil untuk variabel lain, misalnya kredensial basis data, tanpa menyalin pola pemeriksaannya.
// tidak-dijalankan
function bacaEnv(nama: string): string {
const nilai = process.env[nama];
if (nilai === undefined || nilai === "") {
throw new Error(`variabel lingkungan ${nama} wajib diisi`);
}
return nilai;
}
try {
const rahasia = bacaEnv("JWT_SECRET");
console.log(`panjang rahasia: ${rahasia.length} karakter`);
} catch (e) {
console.log(`ditolak: ${e instanceof Error ? e.message : String(e)}`);
}# menjalankan dengan JWT_SECRET terisi
JWT_SECRET=rahasia-2026 node --no-warnings baca-env.ts
# menjalankan tanpa JWT_SECRET
node --no-warnings baca-env.ts
# menjalankan dengan JWT_SECRET berupa string kosong
JWT_SECRET= node --no-warnings baca-env.tspanjang rahasia: 12 karakter
ditolak: variabel lingkungan JWT_SECRET wajib diisi
ditolak: variabel lingkungan JWT_SECRET wajib diisiTiga skenario di atas menjalankan berkas yang sama dengan isi lingkungan yang berbeda. Pemanggilan pertama lolos dan mencetak panjang nilainya, sedangkan dua pemanggilan berikutnya ditolak dengan pesan yang sama, karena nilai tidak terdefinisi dan string kosong diperlakukan setara. Blok catch mempersempit e dengan instanceof Error sebelum message dibaca, sehingga pembacaan pesan tetap aman untuk nilai yang dilempar dalam bentuk apa pun. Penempatan try dan catch di sini dipilih agar setiap skenario mencetak satu baris yang mudah dibandingkan, sedangkan pada aplikasi nyata error semacam ini biasanya dibiarkan merambat sampai titik masuk proses.
Modul konfigurasi yang memvalidasi saat startup#
Memanggil bacaEnv di banyak titik menyebarkan validasi ke mana-mana, sehingga proyek nyata mengumpulkannya dalam satu modul konfigurasi. Modul config.ts mengekspor satu objek konfigurasi yang dibangun saat berkas pertama kali diimpor, dan kegagalan validasi melempar error pada saat itu juga. Pola fail fast, yaitu berhenti segera ketika konfigurasi tidak sah, menghentikan proses sebelum sempat melayani satu permintaan pun.
// tidak-dijalankan
type Mode = "development" | "production";
function bacaEnv(nama: string): string {
const nilai = process.env[nama];
if (nilai === undefined || nilai === "") {
throw new Error(`variabel lingkungan ${nama} wajib diisi`);
}
return nilai;
}
function bacaPort(): number {
const mentah = process.env.PORT ?? "3000";
const port = Number(mentah);
if (!Number.isInteger(port) || port <= 0) {
throw new Error(`PORT harus bilangan bulat positif, diterima: "${mentah}"`);
}
return port;
}
function bacaMode(): Mode {
return process.env.NODE_ENV === "production" ? "production" : "development";
}
export const konfigurasi = {
port: bacaPort(),
rahasia: bacaEnv("JWT_SECRET"),
mode: bacaMode(),
};// tidak-dijalankan
import { konfigurasi } from "./config.ts";
console.log(`mode: ${konfigurasi.mode}`);
console.log(`port: ${konfigurasi.port}`);
console.log(`rahasia dimuat: ${konfigurasi.rahasia.length} karakter`);Objek konfigurasi dibangun dari tiga fungsi pembaca, sehingga berisi port bertipe number, rahasia bertipe string yang dijamin terisi, dan mode terbatas pada union Mode. Penentuan mode membandingkan NODE_ENV dengan "production", dan nilai selain itu diperlakukan sebagai development. Berkas mulai-server.ts hanya mengimpor konfigurasi dan mencetaknya, tanpa menyentuh process.env sama sekali.
# menjalankan aplikasi dengan variabel lingkungan lengkap
JWT_SECRET=rahasia-2026 NODE_ENV=production PORT=8080 node --no-warnings mulai-server.ts
# menjalankan dengan variabel minimum agar PORT dan mode memakai bawaan
JWT_SECRET=rahasia-2026 node --no-warnings mulai-server.tsmode: production
port: 8080
rahasia dimuat: 12 karakter
mode: development
port: 3000
rahasia dimuat: 12 karakterEksekusi kedua memperlihatkan perilaku bawaan, yaitu port 3000 ketika PORT tidak diisi dan mode development ketika NODE_ENV bukan production. Bagian bawaan itu ditentukan sepenuhnya oleh bacaPort dan bacaMode, sehingga tidak ada pemanggil yang perlu mengetahui nilainya. Seluruh keputusan semacam itu terkumpul di config.ts, dan berkas lain cukup mengimpor konfigurasi yang sudah tervalidasi.
Fungsi bacaPort menerapkan pemeriksaan itu di satu tempat, yaitu memvalidasi hasil konversi dengan Number.isInteger sebelum nilainya dipakai. Pesan penolakannya menyertakan nilai mentah yang diterima, sehingga penyebabnya langsung terbaca dari log startup. Dengan begitu kesalahan isi variabel lingkungan selalu terlihat pada detik pertama proses berjalan, bukan ketika permintaan pengguna gagal.
Argumen CLI process.argv#
process.argv adalah array berisi string yang memuat seluruh isi baris perintah, dengan proses Node pada indeks 0, jalur berkas yang dijalankan pada indeks 1, dan argumen milik pengguna mulai indeks 2. Tipenya string[], sehingga pemisahan argumen pengguna dari dua elemen pertama dilakukan lewat slice sebelum dipakai. Fungsi pembaca berikut menangani kedua kemungkinan, yaitu dengan dan tanpa argumen.
function bacaBerkas(argv: string[]): string {
const argumen = argv.slice(2);
if (argumen.length === 0) {
return "penggunaan: node baca-argumen.ts <berkas>";
}
return `mengolah berkas: ${argumen[0]}`;
}
console.log(bacaBerkas(process.argv));penggunaan: node baca-argumen.ts <berkas>Keluaran di atas berasal dari eksekusi tanpa argumen tambahan, sehingga slice(2) menghasilkan array kosong dan fungsi mengembalikan baris penggunaan. Menolak dengan baris penggunaan adalah praktik lazim pada CLI agar pengguna langsung tahu cara memanggil programnya dengan benar. Berkas yang sama dijalankan kembali dengan satu argumen untuk melihat cabang kedua fungsi tersebut.
# menjalankan pembaca argumen dengan satu nama berkas
node baca-argumen.ts catatan.txtmengolah berkas: catatan.txtArgumen catatan.txt masuk sebagai string pada indeks 2 dan langsung terbaca setelah slice. CLI yang lebih lengkap dapat memetakan argumen menjadi objek lengkap dengan opsi bantuan, dan Node.js menyediakan util.parseArgs untuk penguraian opsi semacam itu, tetapi dasarnya tetap sama, yaitu memperlakukan process.argv sebagai string[] yang wajib divalidasi sebelum dipakai. Pola apa pun bermuara pada hal yang sama, yaitu array string yang diubah menjadi bentuk yang dianotasi sebelum dipakai oleh logika aplikasi.
Kesalahan umum#
Kesalahan pertama adalah memperlakukan nilai dari process.env sebagai string yang selalu ada. Tanpa validasi, undefined lolos ke logika aplikasi dan menimbulkan perilaku yang sulit dilacak, karena gejalanya muncul jauh dari titik asalnya. Pembacaan variabel lingkungan dikumpulkan di satu modul konfigurasi seperti config.ts di atas agar modul itu menjadi satu-satunya tempat pemeriksaan.
Kesalahan kedua adalah mengonversi tanpa memeriksa. Number("") menghasilkan 0 dan Number("abc") menghasilkan NaN, keduanya tanpa melempar apa pun, sehingga port kosong terbaca sebagai angka sah dan aplikasi baru gagal saat mengikat port. Konversi selalu disertai Number.isInteger atau pemeriksaan serupa. Kesalahan ketiga adalah membaca argumen dari indeks yang salah, karena process.argv[0] adalah proses Node dan argv[1] adalah berkas skrip, sehingga argumen pengguna diambil mulai indeks 2, misalnya lewat slice(2). Kesalahan keempat adalah menulis ulang pembacaan process.env di banyak berkas, karena validasi yang sama mudah tidak sinkron, dan pemecahannya sudah terlihat pada modul konfigurasi yang mengekspor objek siap pakai.
Kesalahan kelima adalah mencetak seluruh objek konfigurasi ke log saat startup. Nilai rahasia seperti token akses ikut tercetak dan tersimpan pada berkas log. Karena itu, log sebaiknya hanya menampilkan pembacaan per bidang atau menyamarkan nilai sensitifnya. Kebiasaan itu sekaligus menjelaskan mengapa bidang rahasia layak dipisahkan dari bidang biasa seperti port dan mode pada objek konfigurasi.
Ringkasan#
- Tipe
IncomingMessagedanServerResponsedari@types/nodemendokumentasikan callbackcreateServer, dan logika rute dapat dipisah menjadi fungsi murni. process.envbertipeRecord<string, string | undefined>, sehingga setiap nilai divalidasi lewat fungsi pembaca yang melempar error bila kosong.- Modul konfigurasi membangun objek tervalidasi saat diimpor, sehingga kegagalan lingkungan menghentikan proses sebelum melayani permintaan.
process.argvbertipestring[]dengan argumen pengguna mulai indeks 2, sehinggaslicedan validasi selalu diperlukan.
Referensi#
- TypeScript — Node.js API — Cara Node.js menjalankan berkas TypeScript secara langsung beserta batas-batasnya.
- process — Node.js API
— Referensi objek
process, termasukprocess.envdanprocess.argv.