Membangun API Gateway dengan Express.js: Routing, Rate Limiting, dan Service Aggregation

Lhuqita Fazry
Web Development Express.js API Gateway Microservices Node.js
Membangun API Gateway dengan Express.js: Routing, Rate Limiting, dan Service Aggregation

Saat sebuah aplikasi monolitik dipecah menjadi beberapa microservice, muncul tantangan baru: client harus mengingat alamat dan port setiap service, menangani autentikasi berulang kali, dan menghadapi kompleksitas komunikasi lintas service. API Gateway hadir sebagai solusi untuk masalah-masalah ini. Artikel ini akan memandu kita membangun API Gateway sendiri menggunakan Express.js dengan fitur routing, rate limiting, service aggregation, autentikasi, dan logging.

Memahami Peran API Gateway dalam Arsitektur Microservices

Dalam arsitektur microservices, setiap service berjalan sebagai proses terpisah dengan alamat dan port masing-masing. Tanpa API Gateway, client harus berkomunikasi langsung dengan setiap service. Pendekatan ini menyebabkan beberapa masalah: client perlu mengelola banyak URL endpoint, logic autentikasi harus diimplementasikan ulang di setiap service, dan endpoint internal service terekspos ke publik. Semakin banyak service yang dimiliki, semakin kompleks juga pengelolaan di sisi client.

API Gateway bertindak sebagai single entry point yang menjadi gerbang tunggal bagi seluruh request dari client. Gateway menangani routing request ke service yang tepat, memvalidasi token autentikasi, menerapkan rate limiting, dan bahkan menggabungkan data dari beberapa service dalam satu response. Perbandingannya sederhana: tanpa gateway, client harus tahu bahwa User Service ada di user.internal:3001, Order Service di orders.internal:3002, dan seterusnya. Dengan gateway, client cukup mengirim request ke api.example.com/users dan api.example.com/orders — gateway yang menangani distribusinya. Keuntungan utamanya adalah separation of concerns — service backend hanya fokus pada business logic, sementara concern lintas service seperti keamanan dan routing ditangani di satu tempat terpusat.

Diagram arsitektur API Gateway yang menunjukkan client terhadap API Gateway yang terhubung ke berbagai backend services

Gambar: Diagram arsitektur API Gateway yang menunjukkan client terhadap API Gateway yang terhubung ke berbagai backend services — Sumber: [Microsoft Learn - Azure Architecture Center](https://learn.microsoft.com/en-us/azure/architecture/microservices/design/gateway)

Dengan pendekatan ini, tim pengembang dapat menambahkan, mengubah, atau menskalakan service backend tanpa memengaruhi client. Selama gateway tetap menyediakan kontrak API yang konsisten, perubahan di balik layar tidak akan berdampak pada pengguna. Ini juga memudahkan migrasi — kita bisa mengganti implementasi service secara bertahap tanpa mengubah antarmuka yang diekspos ke publik.

Merancang Router Gateway untuk Mendistribusikan Request ke Service Backend

Langkah pertama dalam membangun API Gateway adalah menyiapkan server Express dan mengonfigurasi routing menggunakan http-proxy-middleware. Library ini memungkinkan kita meneruskan request ke service backend berdasarkan path prefix tanpa mengubah isi request.

Struktur routing yang umum digunakan adalah memetakan path tertentu ke service tertentu. Sebagai contoh, request dengan prefix /users akan diteruskan ke User Service, /orders ke Order Service, dan /products ke Product Service.

javascriptjavascript
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

const services = {
  users: 'http://user-service:3001',
  orders: 'http://order-service:3002',
  products: 'http://product-service:3003'
};

app.use('/users', createProxyMiddleware({
  target: services.users,
  changeOrigin: true,
  onError: (err, req, res) => {
    console.error(`Proxy error: ${err.message}`);
    res.status(502).json({
      error: 'Service unavailable',
      service: 'users'
    });
  }
}));

app.use('/orders', createProxyMiddleware({
  target: services.orders,
  changeOrigin: true
}));

app.use('/products', createProxyMiddleware({
  target: services.products,
  changeOrigin: true
}));

app.listen(3000, () => {
  console.log('API Gateway running on port 3000');
});

Setiap middleware proxy meneruskan request ke service tujuan sembari mempertahankan path asli. Parameter changeOrigin: true penting karena mengubah header Host pada request keluar menjadi domain target, sehingga service backend menerima request seolah-olah berasal dari client yang sah. Tanpa parameter ini, beberapa service backend mungkin menolak request karena ketidakcocokan header.

Jika service tujuan tidak reachable, handler onError akan mengembalikan response fallback berupa JSON dengan status 502 Bad Gateway. Struktur response yang konsisten — selalu berbentuk JSON dengan field error dan service — memudahkan frontend untuk menampilkan pesan kesalahan yang sesuai. Client tidak perlu menebak-nebak apakah error disebabkan oleh network timeout, service crash, atau konfigurasi yang salah.

Menerapkan Rate Limiting untuk Melindungi Gateway dari Abuse

Rate limiting adalah mekanisme yang membatasi jumlah request dari satu client dalam jendela waktu tertentu. Penempatan rate limiting di layer gateway sangat strategis karena menjadi lapisan pertahanan terluar sebelum request mencapai service backend. Dengan memblokir request berlebih di gateway, kita melindungi seluruh service dari potensi serangan DDoS, brute force, atau abuse API key.

Diagram throttling yang menunjukkan pembatasan request rate per tenant melalui API Gateway
MERN Stack Development
Web App • Beginner

MERN Stack Development

Launch your journey into full-stack web development with this comprehensive, pro...

Daftar

Gambar: Diagram throttling yang menunjukkan pembatasan request rate per tenant melalui API Gateway — Sumber: [Microsoft Learn - Azure Architecture Center](https://learn.microsoft.com/en-us/azure/architecture/patterns/throttling)

Kita dapat mengimplementasikan rate limiting menggunakan library express-rate-limit. Library ini menyediakan middleware yang dapat dikonfigurasi dengan berbagai parameter, termasuk windowMs untuk jendela waktu dan max untuk jumlah request maksimum.

javascriptjavascript
const rateLimit = require('express-rate-limit');

const globalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  message: { error: 'Too many requests' }
});

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: { error: 'Too many login attempts' }
});

app.use('/api', globalLimiter);
app.use('/auth/login', authLimiter);

Dalam contoh di atas, kita menerapkan dua level pembatasan. Middleware globalLimiter membatasi seluruh endpoint API dengan batas 100 request per 15 menit per IP. Ini melindungi resource komputasi service backend dari lonjakan traffic yang tidak terduga. Sementara itu, authLimiter menerapkan batas yang lebih ketat — hanya 5 request per 15 menit — pada endpoint login untuk mencegah serangan brute force. Endpoint login membutuhkan proteksi ekstra karena sifatnya yang sensitif dan menjadi target umum eksploitasi.

Client yang melampaui batas akan menerima response HTTP 429 Too Many Requests dengan struktur JSON yang jelas. express-rate-limit juga secara otomatis menyertakan header Retry-After yang memberi tahu client berapa detik lagi harus menunggu sebelum mencoba kembali. Header ini sangat penting untuk client yang menerapkan automatic retry logic — tanpa informasi ini, client bisa terus-menerus mengirim request dan memperburuk situasi.

Menggabungkan Response dari Multiple Service dengan Service Aggregation

Salah satu skenario paling umum di aplikasi modern adalah halaman dashboard yang membutuhkan data dari berbagai sumber. Tanpa service aggregation, client harus melakukan tiga request terpisah, meningkatkan latency total dan kompleksitas di sisi frontend. API Gateway dapat menangani penggabungan ini dengan teknik parallel request menggunakan Promise.all.

Diagram Gateway Aggregation Pattern yang menunjukkan client mengirim satu request ke gateway, gateway memanggil multiple backend services secara paralel, dan mengembalikan response gabungan

Gambar: Diagram Gateway Aggregation Pattern yang menunjukkan client mengirim satu request ke gateway, gateway memanggil multiple backend services secara paralel, dan mengembalikan response gabungan — Sumber: [Microsoft Learn - Azure Architecture Center](https://learn.microsoft.com/en-us/azure/architecture/patterns/gateway-aggregation)

javascriptjavascript
const axios = require('axios');

app.get('/dashboard', async (req, res) => {
  const results = await Promise.allSettled([
    axios.get('http://user-service:3001/users/me', {
      headers: { Authorization: req.headers.authorization }
    }),
    axios.get('http://order-service:3002/orders/recent'),
    axios.get('http://product-service:3003/products/recommended')
  ]);

  const [userResult, ordersResult, productsResult] = results;

  res.json({
    user: userResult.status === 'fulfilled'
      ? userResult.value.data : null,
    recentOrders: ordersResult.status === 'fulfilled'
      ? ordersResult.value.data : [],
    recommendedProducts: productsResult.status === 'fulfilled'
      ? productsResult.value.data : [],
    errors: {
      orders: ordersResult.status === 'rejected'
        ? ordersResult.reason.message : null,
      products: productsResult.status === 'rejected'
        ? productsResult.reason.message : null
    }
  });
});

Perhatikan penggunaan Promise.allSettled — bukan Promise.all. Perbedaannya penting: Promise.allSettled menunggu semua promise selesai, baik yang berhasil maupun gagal, tanpa melempar exception. Jika menggunakan Promise.all, satu service yang down akan menggagalkan seluruh request dashboard dan client mendapat response 500 Internal Server Error. Dengan Promise.allSettled, kita menangani setiap hasil secara individual.

Ini memungkinkan kita menerapkan strategi partial failure, yaitu mengembalikan data dari service yang tersedia beserta informasi error untuk service yang bermasalah. Response yang dihasilkan memiliki struktur yang siap dikonsumsi frontend tanpa perlu transformasi tambahan — cukup render data dari field user, recentOrders, dan recommendedProducts, lalu tampilkan pesan error untuk service yang bermasalah. Alih-alih menampilkan halaman kosong saat satu service down, frontend tetap dapat menampilkan data parsial dengan indikasi error pada bagian yang gagal dimuat.

Menambahkan Middleware Autentikasi dan Logging di Level Gateway

Meletakkan autentikasi di level gateway adalah praktik terbaik yang menghilangkan duplikasi logic keamanan di setiap microservice. Cukup satu lapisan validasi JWT di gateway, dan semua service backend di belakangnya aman. Selain itu, logging terpusat di gateway memberikan visibilitas penuh terhadap seluruh traffic yang masuk.

Urutan middleware pipeline sangat penting: Logger harus menjadi middleware pertama, diikuti Auth, Rate Limiter, dan terakhir Router. Urutan ini memastikan setiap request tercatat sebelum diproses lebih lanjut, dan request tanpa token valid ditolak sebelum membebani rate limiter atau service backend.

javascriptjavascript
const jwt = require('jsonwebtoken');

// Middleware Logger
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(
      `[${new Date().toISOString()}] ${req.method} ${req.path} ${res.statusCode} ${duration}ms`
    );
  });
  next();
});

// Middleware Auth
app.use('/api', (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
});

// Pipeline
app.use(logger);
app.use('/api', authMiddleware);
app.use('/api', globalLimiter);
app.use('/users', usersProxy);
app.use('/orders', ordersProxy);
app.use('/products', productsProxy);

Response logging dalam format [timestamp] METHOD /path statusCode durationMs memberikan data berharga untuk debugging dan monitoring performa. Data durasi request, misalnya, bisa kita gunakan untuk mendeteksi service mana yang mulai melambat sebelum benar-benar down. Pola ini juga memudahkan integrasi dengan sistem logging terpusat seperti ELK Stack atau Datadog.

Sementara itu, middleware autentikasi memverifikasi JWT dengan jwt.verify() yang melempar exception jika token tidak valid — baik karena expired, signature salah, atau token sudah ditarik (blacklisted). Payload hasil verifikasi disimpan ke req.user agar bisa diakses oleh service backend. Dengan pola ini, service backend cukup membaca header X-User-Id atau header kustom lain yang diteruskan oleh gateway, tanpa perlu mengimpor dan mengonfigurasi library JWT masing-masing. Ini secara signifikan mengurangi duplikasi kode dan potensi celah keamanan akibat konfigurasi yang tidak seragam antar service.

Strategi Monitoring dan Best Practices untuk Gateway di Production

API Gateway yang berjalan di production membutuhkan strategi monitoring yang solid. Salah satu komponen terpenting adalah health check endpoint yang secara periodik memverifikasi ketersediaan setiap service backend.

javascriptjavascript
app.get('/health', async (req, res) => {
  const services = ['users', 'orders', 'products'];
  const baseUrls = {
    users: 'http://user-service:3001',
    orders: 'http://order-service:3002',
    products: 'http://product-service:3003'
  };

  const checks = await Promise.allSettled(
    services.map(name =>
      axios.get(`${baseUrls[name]}/health`).then(() => name)
    )
  );

  const statusMap = {};
  checks.forEach((result, index) => {
    statusMap[services[index]] =
      result.status === 'fulfilled' ? 'up' : 'down';
  });

  const allUp = Object.values(statusMap).every(s => s === 'up');

  res.json({
    status: allUp ? 'healthy' : 'degraded',
    services: statusMap,
    timestamp: new Date().toISOString()
  });
});

Selain health check, beberapa best practices lain perlu diterapkan. Circuit breaker pattern menghentikan sementara traffic ke service yang terus-menerus gagal, lalu mencoba kembali setelah periode cooldown. Implementasi sederhananya: jika health check gagal tiga kali berturut-turut, circuit breaker membuka sirkuit dan semua request ke service tersebut langsung ditolak dengan response 503 Service Unavailable selama 30 detik. Setelah cooldown, satu request percobaan dikirimkan — jika berhasil, sirkuit ditutup kembali; jika gagal, cooldown diulang dengan durasi yang lebih panjang.

Environment-based configuration menggunakan variabel lingkungan untuk mengatur URL service, secret key, dan parameter rate limiting. Ini memastikan kode yang sama bisa berjalan di development dengan konfigurasi lokal, di staging dengan service tiruan, dan di production dengan service sesungguhnya — tanpa perubahan kode. Structured logging dalam format JSON memudahkan integrasi dengan alat monitoring seperti Elasticsearch atau Datadog, sehingga tim DevOps dapat membuat dashboard visual untuk memantau request count, error rate, dan latency per service secara real-time.

Terakhir, idempotency key pada mutation endpoint mencegah duplikasi request akibat retry dari client. Gateway cukup menyimpan key yang sudah diproses di Redis dengan TTL tertentu, dan mengembalikan response yang sama untuk request duplikat tanpa meneruskannya ke service backend. Pola ini sangat penting untuk endpoint pembayaran atau transaksi finansial di mana duplikasi bisa berakibat fatal.

Praktik langsung membangun API Gateway dengan Express.js adalah keterampilan esensial bagi pengembang yang bekerja dengan arsitektur microservices. Di Rumah Coding, kita membahas topik ini secara mendalam dalam bootcamp backend intensif, mencakup implementasi nyata dengan studi kasus production-grade. Mulai dari routing dasar hingga strategi monitoring lanjutan — semuanya dipraktikkan dalam lingkungan yang terstruktur dan didampingi mentor berpengalaman.

Kursus Terkait

EduStream - Mini Learning Management System (LMS)
Kursus Premium Web App

MERN Stack Development

Launch your journey into full-stack web development with this comprehensive, project-driven course. Designed for beginners, this course demystifies the MERN stack (MongoDB, Express.js, React.js, Node.js) by guiding you step-by-step in building a real-world application from scratch. By the end of this course, you will have the practical skills and a complete portfolio project to confidently step into the modern web development industry.

Proyek Akhir

EduStream - Mini Learning Management System (LMS)

  • Secure Authentication & Authorization: Robust user registration and login using JWT, with strict role-based access control (Admin/Instructor vs. Student).
  • Course Management (Admin Dashboard): Full CRUD (Create, Read, Update, Delete) capabilities for administrators to manage course details, including titles, descriptions, pricing, and thumbnail image uploads.
  • Public Course Catalog: An interactive and responsive storefront where users can browse available courses.
7 Weeks Beginner
Lihat Detail Kursus

Artikel Terkait