DEV Community

Sk Niyaz Noor
Sk Niyaz Noor

Posted on

Managing Multiple APIs and Databases in a Single Frontend Using Redux

Explore how to manage multiple backends, databases, and API calls within a single frontend application using Redux. This article breaks down practical patterns for handling asynchronous data flows, maintaining state consistency, and synchronizing data across diverse services reflecting the architecture commonly used in large-scale, real-world applications.

Modern applications often use multiple backends and databases. A frontend (React, Angular, etc.) may need to fetch and combine data from different APIs and keep everything synchronized in state management like Redux.

A. Understand the Architecture

A typical architecture with multiple data sources looks like this:

Frontend (React)
   |
   |------ API 1 ------ Backend A ------ Database A
   |
   |------ API 2 ------ Backend B ------ Database B
   |
   |------ API 3 ------ Backend C ------ Database C
Enter fullscreen mode Exit fullscreen mode

Example scenario:

Database Data
Database A Players
Database B Sports
Database C Player-Sports relationships

The frontend must combine all data to display meaningful information.

Example output in UI:
MS Dhoni
Sports:

  • Cricket
  • Football

B. Design the Database Properly

A normalized relational structure is essential.

Players Table

players
--------------------------------
id | name
1  | MS Dhoni
2  | Virat Kohli
Enter fullscreen mode Exit fullscreen mode

Sports Table

sports
--------------------------------
id | sport_name
1  | Cricket
2  | Football
3  | Golf
Enter fullscreen mode Exit fullscreen mode

Player_Sports Table (Relationship Table)

player_sports
--------------------------------
player_id | sport_id
1         | 1
1         | 2
2         | 1
Enter fullscreen mode Exit fullscreen mode

This design allows:

  • One player → multiple sports
  • One sport → multiple players

This is called a many-to-many relationship.

C. Create Backend APIs

Each backend service exposes its own API.
Example endpoints:
GET /api/players
GET /api/sports
GET /api/player-sports

Example responses:
/players

[
 { "id": 1, "name": "MS Dhoni" },
 { "id": 2, "name": "Virat Kohli" }
]
Enter fullscreen mode Exit fullscreen mode

/sports

[
 { "id": 1, "sport": "Cricket" },
 { "id": 2, "sport": "Football" }
]
Enter fullscreen mode Exit fullscreen mode

/player-sports

[
 { "player_id": 1, "sport_id": 1 },
 { "player_id": 1, "sport_id": 2 }
]
Enter fullscreen mode Exit fullscreen mode

D. Setup Redux Store Structure

Instead of storing everything together, keep normalized data.
Example Redux store structure:

store
├ players
├ sports
└ playerSports

Example state:

{
 players: [],
 sports: [],
 playerSports: []
}
Enter fullscreen mode Exit fullscreen mode

This makes updates and caching easier.

E. Create Redux Slices

Use Redux Toolkit.
Players Slice

const playersSlice = createSlice({
  name: "players",
  initialState: [],
  reducers: {
    setPlayers: (state, action) => action.payload
  }
})
Enter fullscreen mode Exit fullscreen mode

F. Fetch Multiple APIs

Use asyncThunk to fetch data.
Example:

export const fetchPlayers = createAsyncThunk(
  "players/fetch",
  async () => {
    const res = await fetch("/api/players")
    return res.json()
  }
)
Enter fullscreen mode Exit fullscreen mode

Similarly create:
fetchSports()
fetchPlayerSports()

G. Dispatch API Calls Together

When the page loads, call all APIs.
Example:

useEffect(() => {
  dispatch(fetchPlayers())
  dispatch(fetchSports())
  dispatch(fetchPlayerSports())
}, [])
Enter fullscreen mode Exit fullscreen mode

Now Redux contains all datasets.

H. Combine Data in Selectors

Selectors are used to merge datasets.
Example:

const playersWithSports = players.map(player => {
  const sportsPlayed = playerSports
    .filter(ps => ps.player_id === player.id)
    .map(ps => sports.find(s => s.id === ps.sport_id))
  return {
    ...player,
    sports: sportsPlayed
  }
})
Enter fullscreen mode Exit fullscreen mode

Result:

[
 {
  name: "MS Dhoni",
  sports: ["Cricket", "Football"]
 }
]
Enter fullscreen mode Exit fullscreen mode

I. Render Data in React

Example:

playersWithSports.map(player => (
  <div key={player.id}>
    <h2>{player.name}</h2>
    <ul>
      {player.sports.map(s => (
        <li key={s.id}>{s.sport}</li>
      ))}
    </ul>
  </div>
))
Enter fullscreen mode Exit fullscreen mode

J. Avoid Too Many API Calls (Optimization)

Large applications often use a Backend for Frontend (BFF) or API Gateway.
Architecture:

Frontend
   |
API Gateway
   |
   ---------------------------------
   |              |                |
Service A       Service B      Service C
Enter fullscreen mode Exit fullscreen mode

The gateway merges responses.
Example response:

{
  "name": "MS Dhoni",
  "sports": ["Cricket", "Football"]
}
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • fewer API calls
  • faster UI
  • cleaner frontend code

K. Advanced Redux Optimization

Use:

  • RTK Query
  • Memoized selectors
  • Normalized state

Example normalized state:
entities
├ playersById
├ sportsById
└ playerSports

Benefits:

  • faster lookups
  • less re-rendering
  • scalable architecture

L. Common Problems & Solutions

Problem 1: API loads at different times
Solution:
loading states per slice

Problem 2: Duplicate data
Solution:
Normalize state

Problem 3: Slow frontend
Solution:

  • Use API gateway
  • Cache responses
  • Use RTK Query

M. Real-World Examples

Companies using similar architecture:

Company System
Netflix Microservices APIs
Instagram GraphQL gateway
Amazon Service-based APIs

Frontend merges multiple services.

N. Final Architecture Summary

Now let’s move to the practical production-level guide for large frontend systems that consume many APIs and multiple databases, including:

  • Real production folder structure
  • How large apps manage 20+ APIs efficiently
  • Replacing Redux with RTK Query / React Query
  • Microservices architecture used by big tech

A. Real Production Frontend Folder Structure

A scalable frontend usually follows feature-based architecture, not file-type architecture.
Basic Small Project (Not Scalable ❌)

src
├ components
├ pages
├ redux
├ api
└ utils

This becomes messy with 20+ APIs.

Production-Level Structure (Recommended)

src
├ app
│ ├ store.js
│ └ hooks.js

├ services
│ ├ apiClient.js
│ ├ playersApi.js
│ ├ sportsApi.js
│ └ authApi.js

├ features
│ ├ players
│ │ ├ playersSlice.js
│ │ ├ playersSelectors.js
│ │ ├ PlayersPage.jsx
│ │ └ components
│ │ └ PlayerCard.jsx
│ │
├ sports
│ │ ├ sportsSlice.js
│ │ └ SportsPage.jsx
│ │
├ auth
│ ├ authSlice.js
│ └ LoginPage.jsx

├ components
│ ├ UI
│ ├ layout
│ └ common

├ hooks
│ ├ useAuth.js
│ └ useFetchPlayers.js

├ utils
│ ├ helpers.js
│ └ constants.js

├ routes
│ └ AppRoutes.jsx

└ index.js

Benefits:

  • Each feature owns its logic
  • Easier scaling
  • Easy onboarding for new developers
  • APIs are isolated in services

B. How Large Apps Manage 20+ APIs Efficiently

Large apps don't call APIs randomly.
They use 3 key layers.

API Service Layer
Create a central API client.
services/apiClient.js

Example:

import axios from "axios"
const apiClient = axios.create({
  baseURL: "/api",
  timeout: 10000
})
export default apiClient
Enter fullscreen mode Exit fullscreen mode

Separate APIs by domain
Example:

services
├ usersApi.js
├ ordersApi.js
├ playersApi.js
├ sportsApi.js
└ analyticsApi.js

Example:

import api from "./apiClient"
export const getPlayers = () => api.get("/players")
export const getSports = () => api.get("/sports")
Enter fullscreen mode Exit fullscreen mode

Benefits
With 20+ APIs:

Problem Solution
Messy API calls API service layer
Duplicate code Shared API client
Difficult caching Query libraries

C. Replace Redux with RTK Query

Redux Toolkit introduced RTK Query for API state.
Advantages:

✔ Automatic caching
✔ Automatic refetching
✔ No reducers needed
✔ No thunks needed

Example RTK Query Setup
services/apiSlice.js

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
export const api = createApi({
  reducerPath: "api",
  baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
  endpoints: (builder) => ({
    getPlayers: builder.query({
      query: () => "/players"
    }),
    getSports: builder.query({
      query: () => "/sports"
    })
  })
})
export const { useGetPlayersQuery, useGetSportsQuery } = api
Enter fullscreen mode Exit fullscreen mode

Using It in Components

const { data: players, isLoading } = useGetPlayersQuery()

No reducers needed.

D. Using React Query Instead

Many companies prefer React Query (TanStack Query).
Example:

import { useQuery } from "@tanstack/react-query"
import axios from "axios"
const fetchPlayers = async () => {
  const res = await axios.get("/api/players")
  return res.data
}
export const usePlayers = () =>
  useQuery({
    queryKey: ["players"],
    queryFn: fetchPlayers
  })
Enter fullscreen mode Exit fullscreen mode

Usage:

const { data, isLoading } = usePlayers()

Why React Query is popular

Feature Benefit
Caching automatic
Background refresh automatic
Deduping automatic
Pagination easy

E. Backend Microservices Architecture

Large companies use microservices instead of one backend.
Example architecture:

Each service has its own database.
This is called:
Database per service pattern

Used by companies like:

  • Netflix
  • Amazon
  • Uber
  • Airbnb

F. API Gateway Pattern

Frontend should not call 20 services directly.
Instead use:

Benefits:
✔ fewer API calls
✔ better security
✔ central auth
✔ response aggregation

G. Backend for Frontend (BFF)

Large systems often create a BFF layer.
Example:

          Frontend (Web)
         Frontend (Mobile)
                 |
              BFF Layer
                 |
           Microservices
Enter fullscreen mode Exit fullscreen mode

Each frontend gets custom API responses.
Used heavily by:

  • Netflix
  • Spotify

H. Example End-to-End Architecture

I. Performance Techniques Used by Big Tech

Caching
Redis
CDN
Browser Cache
Request batching

Instead of:
10 API calls

Use:
1 aggregated API
GraphQL
Many companies use GraphQL to fetch only needed data.
Example:

query {
  player {
    name
    sports
  }
}
Enter fullscreen mode Exit fullscreen mode

Used by:

  • Meta Platforms
  • Shopify

J. Final Best Production Stack

Modern frontend stack:

React
React Query / RTK Query
TypeScript
Axios
Feature-based architecture
API Gateway backend
Microservices

Now let us move to next step

  • How to design a frontend that scales to 1 million users
  • How to manage 100+ APIs without chaos

Managing 100+ APIs is a real challenge in large-scale systems. Companies like Netflix, Amazon, and Uber solve this with structured architecture, tooling, and conventions. Below is a practical production approach used in large applications.

A. Organize APIs by Domain (Domain-Driven Design)

Never keep all APIs in one folder.
Instead group them by business domain.

Bad structure ❌

services
├ api1.js
├ api2.js
├ api3.js
├ api4.js
└ api100.js

Impossible to maintain.

Correct structure ✅

services
├ auth
│ ├ authApi.js
│ └ authTypes.js

├ users
│ ├ usersApi.js
│ └ usersTypes.js

├ orders
│ ├ ordersApi.js
│ └ ordersTypes.js

├ payments
│ ├ paymentsApi.js
│ └ paymentsTypes.js

├ analytics
│ ├ analyticsApi.js
│ └ analyticsTypes.js

Each domain owns its APIs.

Benefits:
Easier maintenance
Clear ownership
Smaller modules

B. Use a Central API Client

Avoid repeating configuration.
Create a single HTTP client.

// apiClient.js
import axios from "axios"
const apiClient = axios.create({
  baseURL: "/api",
  timeout: 10000
})
apiClient.interceptors.request.use(config => {
  const token = localStorage.getItem("token")
  if (token) config.headers.Authorization = `Bearer ${token}`
  return config
})
export default apiClient
Enter fullscreen mode Exit fullscreen mode

Now all APIs use the same client.

C. API Layer Pattern

Never call APIs directly in components.
Correct flow:

Example:
Service

// usersApi.js
import apiClient from "../apiClient"
export const getUsers = () => apiClient.get("/users")
export const getUser = (id) => apiClient.get(`/users/${id}`)
Enter fullscreen mode Exit fullscreen mode

Hook

// useUsers.js
import { useQuery } from "@tanstack/react-query"
import { getUsers } from "../services/users/usersApi"
export const useUsers = () =>
  useQuery({
    queryKey: ["users"],
    queryFn: getUsers
  })
Enter fullscreen mode Exit fullscreen mode

D. Use React Query or RTK Query for Caching

When managing 100 APIs, manual state becomes impossible.
Libraries like:

React Query
RTK Query

handle:
✔ caching
✔ deduplication
✔ background refresh
✔ pagination
✔ loading states

Example:

const { data, isLoading } = useQuery({
  queryKey: ["players"],
  queryFn: fetchPlayers
})
Enter fullscreen mode Exit fullscreen mode

E. API Versioning

Large systems need version control.

Example:
/api/v1/users
/api/v1/orders
/api/v2/users

This prevents breaking old clients.

F. Use an API Gateway

Frontend should not talk to 100 services directly.
Use an API Gateway.

Architecture:

The gateway:

✔ aggregates responses
✔ handles authentication
✔ rate limits
✔ routing
This pattern is heavily used by Netflix.

G. Backend for Frontend (BFF)

Different frontends need different responses.

Example:
Web Frontend
Mobile App
Admin Panel

Each uses a BFF service.

Benefits:
optimized responses
fewer API calls
faster UI

H. API Documentation & Discovery

When you have 100 APIs, developers must discover them easily.
Use tools like:

OpenAPI / Swagger
API catalogs
internal developer portals

Example:

/api-docs
Enter fullscreen mode Exit fullscreen mode

This shows:
endpoints
parameters
Responses

I. Naming Conventions

Consistency prevents chaos. Use standard naming patterns to ensure clarity across multiple teams and services.

RESTful Convention (Good) Non-RESTful (Avoid)
GET /users /fetchUsersList
GET /users/{id} /getUser
POST /users /createUserRecord
PUT /users/{id} /updateUser
DELETE /users/{id} /removeUser

J. Request Aggregation

Instead of:
5 API calls

Use:
1 aggregated endpoint

Example response:

{
  "user": {...},
  "orders": [...],
  "payments": [...]
}
Enter fullscreen mode Exit fullscreen mode

This reduces network overhead.

K. Use Feature-Based Frontend Architecture

Each feature manages its own APIs.

Example:

features
├ users
│ ├ usersApi.js
│ ├ useUsers.js
│ └ UsersPage.jsx

├ orders
│ ├ ordersApi.js
│ ├ useOrders.js
│ └ OrdersPage.jsx

This keeps large apps maintainable.

L. Monitoring & Observability

For 100+ APIs you must monitor everything.

Common tools:
logging systems
distributed tracing
performance dashboards
Large companies rely heavily on these.

M. Rate Limiting & Security

With many APIs you must protect them.

Common protections:
✔ API keys
✔ OAuth tokens
✔ rate limits
✔ request validation

N. Testing Strategy

Every API should have:

  • unit tests
  • integration tests
  • contract tests

Automated pipelines verify APIs before deployment.

Final Architecture Example

Key Principles for Managing 100+ APIs

  • Domain-based organization
  • Central API client
  • Service layer abstraction
  • Query library for caching
  • API gateway
  • Backend-for-frontend pattern
  • Strong documentation

Managing complexity in multi-source frontend applications requires a shift from simple data fetching to a structured architectural approach. By implementing patterns like an API Gateway or Backend for Frontend (BFF), developers can aggregate disparate data sources and reduce network overhead. Furthermore, leveraging modern tooling such as React Query or RTK Query automates critical tasks like caching and state synchronization. Ultimately, these practices ensure that large-scale systems remain both scalable and maintainable as the number of services and APIs continues to grow.

Top comments (0)