Skip to content

Express API Based on Node.js

🧩 1. Why I Use Express

Lately, I’ve really enjoyed using Express, a Node.js-based backend framework, for small-scale backend development.

My typical use cases:

  • Providing endpoints for client testing (pagination, mock responses, etc.)
  • Wrapping third-party APIs and returning data to clients

Node.js is very developer-friendly for these scenarios because it’s lightweight, non-blocking, and easy to deploy.


🎯 Motivation

When developing frontends, like Android apps, I often find that using mock data isn’t enough. Many pages update dynamically in real-time:

Example: a membership scenario

  • User privileges may change instantly
  • Mocking constant values on the client for testing is inefficient
  • Instead, manipulating the API response allows you to simulate changes and trigger client updates as needed

🏗️ Backend For Frontend (BFF)

Node.js can serve as a lightweight BFF layer, aggregating data for clients:

  • Avoid having frontends (Web, Android, iOS) do repeated data composition
  • Centralized API orchestration is simpler and more maintainable
  • Reduces duplicated logic across platforms

In practice, I’ve encountered projects where all three clients had to assemble data independently, which was inefficient.

Example: TheBump Android project:

  • Backend couldn’t provide a single endpoint due to multiple databases
  • Web, iOS, and Android all needed separate data composition

🔹 Example: Basic Express Server with TypeScript

typescript
import express, { Request, Response } from "express";

const app = express();
const PORT = process.env.PORT || 3000;

app.get("/", (req: Request, res: Response) => {
  res.send("Hello, Express with TypeScript!");
});

app.get("/api/fetchDataA", async (req: Request, res: Response) => {
    await setTimeout(() => { 
        res.json({ "data": "dataA" })
    }, 2000)
});

app.get("/api/fetchDataB", async (req: Request, res: Response) => {
    await setTimeout(() => {  
        res.json({ "data": "dataB" })
    }, 5000)
});

app.listen(PORT, () => {
  console.log(`Server is running at http://localhost:${PORT}`);
});

Android Repository Pattern for Parallel Requests

kotlin
interface XXXModuleAPIService {
  @GET("/xxx/xxx/fetchDataA")
  suspend fun fetchDataA(): String
  
  @GET("/xxx/xxx/fetchDataB")
  suspend fun fetchDataB(): String
}

class XXXModuleRepository(
    private val _xxxModuleApiService: XXXModuleAPIService = XXXModuleAPIService()
) {
  suspend fun fetchData(): String = withContext(Dispatchers.IO) {
    val dataA = async { _xxxModuleApiService.fetchDataA() }
    val dataB = async { _xxxModuleApiService.fetchDataB() }

    return@withContext dataA.await() + dataB.await()
  }
}

Problem: Each API call has its own latency (2s + 5s), and the client handles parallelism manually.


🔹 Express API: Aggregated Endpoint

typescript
export interface Data {
    data: string
}

app.get('/api/fetchData', async (req: Request, res: Response) => {
    const start = Date.now();
    
    const [dataA, dataB] = await Promise.all([
        fetch("http://localhost:3000/api/fetchDataA"),
        fetch("http://localhost:3000/api/fetchDataB"),
    ]);

    const [dataABean, dataBBean]: [Data, Data] = await Promise.all([
        dataA.json(),
        dataB.json(),
    ]);

    res.status(200).json({
        "data": dataABean.data + dataBBean.data,
        "time": Date.now() - start
    });
});

Android Usage

kotlin
interface XXXModuleAPIService {
  @GET("/xxx/xxx/fetchData")
  suspend fun fetchData(): String
}

class XXXModuleRepository(
    private val _xxxModuleApiService: XXXModuleAPIService = XXXModuleAPIService()
) {
  suspend fun fetchData(): String = withContext(Dispatchers.IO) {
    return@withContext _xxxModuleApiService.fetchData()
  }
}

Now the client calls a single endpoint, which internally fetches multiple APIs in parallel, improving performance and reducing client-side complexity.


Key Takeaways:

  • Express + Node.js is ideal for lightweight backend services or BFF patterns
  • Aggregating multiple endpoints on the server reduces client-side logic
  • Using Promise.all in Node.js allows parallel requests efficiently
  • Frontend code becomes simpler, cleaner, and faster to develop

This pattern is particularly useful when data is dynamic and multiple clients consume the same APIs.

Just something casual. Hope you like it. Built with VitePress