DEV Community

Cover image for Pre-Integration: API Integration without a Frontend
Raghavendra Singh Dasila
Raghavendra Singh Dasila

Posted on

Pre-Integration: API Integration without a Frontend

In a lot of teams, there is a frontend developer and a backend developer working separately on their tasks. Frontend developer diligently working on the UI implementation (perhaps using AI tools to create the components and screens as well) perfecting it in StoryBook and then polishing it with animations and making things seamless to use in general.

Frontend and backend working together

In the modern tech landscape, creating APIs generally boils down to implementing the business logic and edge cases. Most general APIs can be generated from your data models and permission schema and things like authentication are provided by libraries. That being said, your backend modules are generally re-usable.

Backend guy admiring frontend guy handling browser versions

But when it comes to integrating APIs the traditional approach is to get the API schema either in Postman or Swagger and integrate screen by screen.

But what if you could "pre-integrate" APIs and then plug and play into the frontend of your choice?

This approach works best with MobX based state management architecture but can also be implemented with Redux architectures (caveat is the placement of dispatch actions, slicing and middleware code)

Pre-integration using JSON objects

To "pre-integrate" APIs, you need to first ensure that when implementing UIs any data-related texts should be from a JSON object in your UI and utilize dummy functions to simulate method calls.

This approach is very beneficial as it keeps your code data driven while not bogging you down from your other frontend tasks.

//screens/UserScreen.js

import React, { useState, useEffect } from 'react';
// Simulated JSON data with methods
const userStore = {
  userList: [
    {
      id: 1,
      name: "John Doe",
      age: 30,
      greet: function () {
        return `Hello, I'm ${this.name} and I am ${this.age} years old.`);
      }
    },
    {
      id: 2,
      name: "Jane Smith",
      age: 25,
      greet: function () {
        console.log(`Hi, I'm ${this.name} and I am ${this.age} years old.`);
      }
    }
  ],
  //simulate API Call
  fetchUsers: () => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(userStore.userList);  // Simulate fetching JSON data
      }, 1000);  // Simulate delay
    });
  }
}


// Main React component
const UserComponent = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Fetch data from simulated JSON
    userStore.fetchUsers().then((data) => {
      setLoading(false);
    });
  }, []);

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h1>User List</h1>
      <ul>
        {userStore.userList.map((user) => (
          <li key={user.id}>
            {user.name} ({user.age} years old) 
            <button onClick={() => user.greet()}>Greet</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default UserComponent;

Enter fullscreen mode Exit fullscreen mode

The structure of JSON should be ideally be defined as per the model schema created in the architecture phase, even if the structure changes backend and frontend can independently update their relevant code base.

Now for the actual API "pre-integration" part, we will follow the approach used by Ignite CLI for React Native (can easily be implemented for React as well).

First, some boilerplating:

/*
Project Structure
/app
  /models
    /user
      user-model.js       // User model with actions and flow
      user-store.js       // UserStore with fetch logic
  /screens
    UserScreen.js         // React Native screen that uses the MobX store
  /stores
    root-store.js         // Root store combining all stores
    setup-root-store.js   // Function to set up the root store
*/

//app/stores/root-store.js
import { types } from "mobx-state-tree";
import UserStore from "../models/user/user-store";

// RootStore combining all stores (for now only UserStore)
const RootStore = types.model({
  userStore: UserStore,
});

//app/stores/setup-root-store.js
export default RootStore;
import { RootStore } from "./root-store";

export const setupRootStore = () => {
  const rootStore = RootStore.create({
    userStore: {
      users: [],
      loading: false,
      error: null,
    },
  });

  return rootStore;
};

//app.js or app .tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { setupRootStore } from './stores/setup-root-store';
import { Provider } from 'mobx-react-lite';
import UserScreen from './screens/UserScreen';

const App = () => {
  const rootStore = setupRootStore();

  return (
    <Provider value={rootStore}>
      <NavigationContainer>
        <UserScreen />
      </NavigationContainer>
    </Provider>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

Store Module

Now that's setup we can create our store, let's first define our data model.

//app/models/user/user-model.js
import { types } from "mobx-state-tree";

// Define the User model with a greet action
const User = types
  .model({
    id: types.identifierNumber,
    name: types.string,
    age: types.number,
  })
  .actions((self) => ({
    greet() {
      console.log(`Hello, I'm ${self.name} and I am ${self.age} years old.`);
    },
  }));

export default User;
Enter fullscreen mode Exit fullscreen mode

Now define the actual store which we will be replacing the dummy userStore defined earlier.

//app/models/user/user-store.js
import { types, flow } from "mobx-state-tree";
import User from "./user-model";

// UserStore to handle the state and data fetching
const UserStore = types
  .model({
    users: types.array(User),  // List of User models
    loading: types.boolean,
    error: types.maybeNull(types.string),
  })
  .actions((self) => ({
    // flow for handling async API calls
    fetchUsers: flow(function* () {
      self.loading = true;
      try {
        // Fetch real data from an external API
        const response = yield fetch('https://your-server-here/users');
        const data = yield response.data;
        self.users= User.create(data)
      } catch (error) {
        self.error = "Failed to fetch users.";
      } finally {
        self.loading = false;
      }
    }),
  }));

export default UserStore;
Enter fullscreen mode Exit fullscreen mode

Plug and Play!

Now simply plug and play your pre-integrated module by replacing the userStore JSON with the actual userStore.

//screens/UserScreen.js
import React, { useState, useEffect } from 'react';
//JSON data deleted

// Main React component
const UserComponent = () => {
  const {userStore}=useStores()
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Fetch data from simulated JSON
    userStore.fetchUsers().then((data) => {
      setLoading(false);
    });
  }, []);

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h1>User List</h1>
      <ul>
        {userStore.userList.map((user) => (
          <li key={user.id}>
            {user.name} ({user.age} years old) 
            <button onClick={() => user.greet()}>Greet</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default UserComponent;
Enter fullscreen mode Exit fullscreen mode

The userStore can be developed independently as well and even easily by a backend developer (often backend leads frontend) improving your efficiency.

Enjoy efficient coding!

Top comments (0)