BACKEND-FOR-FRONTEND BFF NODEJS API-DEVELOPMENT MICROSERVICES FRONTEND-OPTIMIZATION ARCHITECTURE WEB-DEVELOPMENT FULLSTACK DEVELOPER-EXPERIENCE

Membangun Backend-for-Frontend (BFF) dengan Node.js: Mengoptimalkan API untuk Kebutuhan Spesifik Frontend

⏱️ 14 menit baca
👨‍💻

Membangun Backend-for-Frontend (BFF) dengan Node.js: Mengoptimalkan API untuk Kebutuhan Spesifik Frontend

1. Pendahuluan

Pernahkah Anda merasa frustrasi saat membangun frontend yang harus berinteraksi dengan banyak API backend yang berbeda? Atau mungkin Anda harus melakukan banyak logika penggabungan data (data orchestration) di sisi klien, yang membuat kode frontend Anda semakin gemuk dan sulit di-maintain? Jika ya, Anda tidak sendirian. Ini adalah masalah umum dalam arsitektur aplikasi modern, terutama saat menggunakan microservices.

Di sinilah pola Backend-for-Frontend (BFF) hadir sebagai solusi elegan. BFF adalah sebuah lapisan API khusus yang dirancang untuk memenuhi kebutuhan spesifik satu jenis klien frontend (misalnya, aplikasi web, aplikasi mobile iOS, atau aplikasi mobile Android). Daripada memaksa semua klien menggunakan satu API umum, BFF memungkinkan kita untuk “menyesuaikan” API agar benar-benar pas dengan apa yang dibutuhkan frontend, mengurangi beban kerja di sisi klien, dan meningkatkan performa.

Dalam artikel ini, kita akan menyelami lebih dalam konsep BFF, mengapa ini penting, kapan harus menggunakannya, dan yang paling penting, bagaimana cara membangun BFF sederhana menggunakan Node.js dan Express. Mari kita mulai! 🚀

2. Apa Itu Backend-for-Frontend (BFF)?

📌 BFF (Backend-for-Frontend) adalah pola arsitektur di mana kita membuat backend terpisah untuk setiap jenis klien frontend. Bayangkan Anda memiliki toko baju online. Anda mungkin punya aplikasi web, aplikasi mobile iOS, dan aplikasi mobile Android. Masing-masing aplikasi ini memiliki kebutuhan data dan interaksi yang sedikit berbeda.

Tanpa BFF, ketiga klien ini mungkin akan memanggil satu set API backend (misalnya, API untuk produk, pengguna, keranjang, pembayaran). Masalahnya, API umum ini mungkin tidak menyediakan data persis seperti yang dibutuhkan oleh setiap frontend.

💡 Contoh Konkret:

Tanpa BFF, frontend harus:

  1. Melakukan banyak panggilan ke berbagai API backend.
  2. Menggabungkan data dari berbagai panggilan tersebut.
  3. Memfilter dan memformat data agar sesuai dengan UI.

Dengan BFF, kita membuat “proxy” atau “adaptor” API untuk setiap frontend. Jadi, frontend web akan memanggil bff-web.com/products/{id}, dan BFF web ini yang akan bertanggung jawab memanggil API backend produk, rating, dan ulasan, menggabungkan datanya, dan mengirimkan respons yang sudah dioptimalkan ke frontend web.

BFF BUKAN API Gateway: Meskipun keduanya berada di “depan” API backend, API Gateway lebih berfokus pada routing, keamanan global, load balancing, dan throttling untuk semua klien. BFF lebih spesifik, melayani satu jenis klien dan melakukan transformasi data yang lebih kompleks sesuai kebutuhan klien tersebut. API Gateway bisa saja berada di depan BFF.

3. Mengapa Kita Membutuhkan BFF? Masalah yang Dipecahkan

Pola BFF muncul untuk mengatasi beberapa tantangan umum dalam pengembangan aplikasi modern:

a. Over-fetching dan Under-fetching

🎯 Solusi BFF: BFF memungkinkan kita membuat endpoint yang tepat-guna, hanya mengembalikan data yang dibutuhkan oleh klien spesifik, dalam format yang optimal.

b. Kompleksitas Frontend

Ketika frontend harus berinteraksi dengan 10-20 microservices yang berbeda, kode untuk mengorkestrasi panggilan, menggabungkan data, dan menangani error menjadi sangat kompleks. Ini memperlambat pengembangan dan meningkatkan peluang bug.

🎯 Solusi BFF: BFF memindahkan logika orkestrasi ini ke sisi server, sehingga frontend bisa fokus pada rendering UI dan interaksi pengguna. Kode frontend menjadi lebih ramping, bersih, dan mudah di-maintain.

c. Kebutuhan Spesifik Klien

Aplikasi mobile (iOS/Android) seringkali memiliki kebutuhan data dan performa yang berbeda dari aplikasi web. Misalnya, mobile mungkin membutuhkan gambar yang lebih kecil, atau data yang dikemas lebih ringkas untuk menghemat bandwidth.

🎯 Solusi BFF: Setiap BFF dapat disesuaikan untuk mengoptimalkan respons sesuai dengan karakteristik dan batasan kliennya (misalnya, bandwidth terbatas untuk mobile, atau resolusi layar berbeda).

d. Isolasi Perubahan

Jika API backend berubah, semua klien yang menggunakannya bisa terpengaruh. Dengan BFF, perubahan di backend hanya perlu diadaptasi di BFF yang relevan, sementara klien frontend tetap stabil. Ini meningkatkan agilitas pengembangan.

🎯 Solusi BFF: BFF bertindak sebagai lapisan adaptasi, mengisolasi klien dari perubahan internal di backend.

4. Kapan Menggunakan (dan Tidak Menggunakan) BFF?

BFF adalah pola yang kuat, tetapi seperti semua pola desain, ia memiliki trade-off.

Gunakan BFF jika:

Jangan gunakan BFF jika:

5. Membangun BFF Sederhana dengan Node.js dan Express

Mari kita bangun contoh BFF sederhana untuk skenario e-commerce. Kita akan membuat BFF untuk aplikasi web yang perlu menampilkan detail produk, menggabungkan data dari dua microservices: Product Service dan Review Service.

Skenario: Frontend web membutuhkan detail produk lengkap dengan rating rata-rata dan jumlah ulasan.

Struktur Proyek:

bff-web-ecommerce/
├── src/
│   ├── index.js
│   └── services/
│       ├── productService.js
│       └── reviewService.js
├── package.json
└── .env

1. package.json:

{
  "name": "bff-web-ecommerce",
  "version": "1.0.0",
  "description": "Backend-for-Frontend for E-commerce Web App",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon src/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.6.8",
    "dotenv": "^16.4.5",
    "express": "^4.19.2"
  },
  "devDependencies": {
    "nodemon": "^3.1.0"
  }
}

2. .env:

PRODUCT_SERVICE_URL=http://localhost:3001
REVIEW_SERVICE_URL=http://localhost:3002
PORT=4000

3. src/services/productService.js: (Simulasi microservice)

// Ini adalah simulasi Product Service yang sebenarnya akan di-host di server terpisah.
// Di BFF, kita akan memanggil endpoint ini.

const products = [
    { id: 'p1', name: 'Laptop Gaming X', description: 'Laptop canggih untuk gamer.', price: 1500, imageUrl: 'https://via.placeholder.com/150/0000FF/FFFFFF?text=Laptop' },
    { id: 'p2', name: 'Smartphone Pro Y', description: 'Ponsel pintar dengan kamera terbaik.', price: 900, imageUrl: 'https://via.placeholder.com/150/FF0000/FFFFFF?text=Smartphone' },
];

class ProductService {
    static getProductById(id) {
        return new Promise((resolve) => {
            setTimeout(() => {
                const product = products.find(p => p.id === id);
                if (product) {
                    resolve({ status: 200, data: product });
                } else {
                    resolve({ status: 404, data: { message: 'Product not found' } });
                }
            }, 500); // Simulasi delay jaringan
        });
    }
}

// Untuk demonstrasi, kita bisa jalankan Product Service secara terpisah
// const express = require('express');
// const app = express();
// app.get('/products/:id', async (req, res) => {
//     const { status, data } = await ProductService.getProductById(req.params.id);
//     res.status(status).json(data);
// });
// app.listen(3001, () => console.log('Product Service running on port 3001'));

module.exports = ProductService;

4. src/services/reviewService.js: (Simulasi microservice)

// Ini adalah simulasi Review Service yang sebenarnya akan di-host di server terpisah.
// Di BFF, kita akan memanggil endpoint ini.

const reviews = {
    'p1': { averageRating: 4.8, reviewCount: 120 },
    'p2': { averageRating: 4.5, reviewCount: 80 },
};

class ReviewService {
    static getProductReviewSummary(productId) {
        return new Promise((resolve) => {
            setTimeout(() => {
                const summary = reviews[productId];
                if (summary) {
                    resolve({ status: 200, data: summary });
                } else {
                    resolve({ status: 200, data: { averageRating: 0, reviewCount: 0 } }); // Tidak ada ulasan
                }
            }, 300); // Simulasi delay jaringan
        });
    }
}

// Untuk demonstrasi, kita bisa jalankan Review Service secara terpisah
// const express = require('express');
// const app = express();
// app.get('/reviews/product/:productId/summary', async (req, res) => {
//     const { status, data } = await ReviewService.getProductReviewSummary(req.params.productId);
//     res.status(status).json(data);
// });
// app.listen(3002, () => console.log('Review Service running on port 3002'));

module.exports = ReviewService;

5. src/index.js: (BFF Utama)

require('dotenv').config();
const express = require('express');
const axios = require('axios');

const app = express();
const PORT = process.env.PORT || 4000;
const PRODUCT_SERVICE_URL = process.env.PRODUCT_SERVICE_URL;
const REVIEW_SERVICE_URL = process.env.REVIEW_SERVICE_URL;

app.use(express.json());

// Endpoint BFF untuk detail produk
app.get('/web/products/:id', async (req, res) => {
    const productId = req.params.id;

    try {
        // 1. Panggil Product Service
        const productResponse = await axios.get(`${PRODUCT_SERVICE_URL}/products/${productId}`);
        const productData = productResponse.data;

        // 2. Panggil Review Service
        const reviewSummaryResponse = await axios.get(`${REVIEW_SERVICE_URL}/reviews/product/${productId}/summary`);
        const reviewSummaryData = reviewSummaryResponse.data;

        // 3. Gabungkan dan format data sesuai kebutuhan frontend
        const combinedProductData = {
            id: productData.id,
            name: productData.name,
            description: productData.description,
            price: productData.price,
            imageUrl: productData.imageUrl,
            rating: reviewSummaryData.averageRating,
            totalReviews: reviewSummaryData.reviewCount
        };

        // 4. Kirim respons yang sudah dioptimalkan ke frontend
        res.json(combinedProductData);

    } catch (error) {
        if (error.response) {
            // Error dari microservice backend
            console.error(`Error calling backend service: ${error.response.status} - ${error.response.data.message}`);
            res.status(error.response.status).json({ message: error.response.data.message });
        } else if (error.request) {
            // Tidak ada respons dari microservice
            console.error(`No response from backend service: ${error.message}`);
            res.status(500).json({ message: 'Backend service unavailable' });
        } else {
            // Error lainnya
            console.error(`Unexpected error: ${error.message}`);
            res.status(500).json({ message: 'Internal server error' });
        }
    }
});

app.listen(PORT, () => {
    console.log(`BFF for Web E-commerce running on port ${PORT}`);
    console.log(`Product Service URL: ${PRODUCT_SERVICE_URL}`);
    console.log(`Review Service URL: ${REVIEW_SERVICE_URL}`);
});

// Jalankan simulasi microservices jika belum ada
// (Ini hanya untuk demo, di produksi microservices akan berjalan terpisah)
const productExpress = require('express');
const reviewExpress = require('express');

const productApp = productExpress();
productApp.use(productExpress.json());
productApp.get('/products/:id', async (req, res) => {
    const ProductService = require('./services/productService');
    const { status, data } = await ProductService.getProductById(req.params.id);
    res.status(status).json(data);
});
productApp.listen(3001, () => console.log('Simulated Product Service running on port 3001'));

const reviewApp = reviewExpress();
reviewApp.use(reviewExpress.json());
reviewApp.get('/reviews/product/:productId/summary', async (req, res) => {
    const ReviewService = require('./services/reviewService');
    const { status, data } = await ReviewService.getProductReviewSummary(req.params.productId);
    res.status(status).json(data);
});
reviewApp.listen(3002, () => console.log('Simulated Review Service running on port 3002'));

Cara Menjalankan:

  1. Instal dependensi: npm install
  2. Jalankan BFF: npm start
  3. Akses dari browser/Postman: http://localhost:4000/web/products/p1

Output yang Diharapkan:

{
    "id": "p1",
    "name": "Laptop Gaming X",
    "description": "Laptop canggih untuk gamer.",
    "price": 1500,
    "imageUrl": "https://via.placeholder.com/150/0000FF/FFFFFF?text=Laptop",
    "rating": 4.8,
    "totalReviews": 120
}

Seperti yang Anda lihat, frontend kini hanya perlu memanggil satu endpoint /web/products/p1 dan langsung mendapatkan data yang sudah digabungkan dan diformat dengan rapi! ✨

6. Best Practices untuk BFF

Membangun BFF tidak hanya tentang menggabungkan API, tetapi juga tentang membangunnya dengan benar:

Kesimpulan

Pola Backend-for-Frontend (BFF) adalah alat yang sangat berharga dalam toolkit arsitektur modern, terutama di era microservices. Dengan membangun lapisan API yang disesuaikan untuk setiap klien frontend, kita dapat secara signifikan mengurangi kompleksitas di sisi klien, mengoptimalkan performa, dan meningkatkan pengalaman developer secara keseluruhan.

Meskipun BFF menambahkan satu lapisan layanan lagi yang perlu dikelola, manfaatnya dalam hal agilitas, skalabilitas, dan developer experience seringkali jauh melebihi biaya tambahannya, terutama untuk aplikasi dengan berbagai jenis klien. Dengan Node.js dan Express, Anda dapat dengan cepat membangun dan mengelola BFF yang efisien untuk proyek Anda. Jadi, jika frontend Anda mulai kewalahan dengan orkestrasi data, mungkin ini saatnya Anda mempertimbangkan BFF!

🔗 Baca Juga