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
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
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
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
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.