In the ever-evolving world of front-end development, ensuring seamless API integration is paramount. With the power of TypeScript, we can craft an elegant API service that not only simplifies our fetch calls but also provides robust error handling. In this guide, we’ll walk through creating such a service, step by step.

The Need for an Elegant API Service

As our applications grow in complexity, so does the need for efficient and organized API calls. By centralizing our API logic, we can ensure consistency, reduce redundancy, and make our codebase more maintainable.

Diving into the Code

import type { ApiError, ApiResponse, User } from '../types';
import { HttpStatusCode } from './HttpStatusCode';

const base_url = import.meta.env.VITE_API_URL;
const header = {
  Accept: 'application/vnd.api+json',
  'Content-Type': 'application/vnd.api+json',
};

const url = async (params: string) => {
  return `${base_url}/${params}`;
};
const token = () => {
  return import.meta.env.VITE_API_TOKEN; // or some other secure function to get your token
};
const fetchFromApi = async <T>(
  url: string,
  options: RequestInit
): Promise<ApiResponse<T> | ApiError> => {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      switch (response.status) {
        case HttpStatusCode.UNAUTHORIZED:
          throw new Error('Invalid API key');
        case HttpStatusCode.NOT_FOUND:
          throw new Error('Endpoint not found');
        default:
          throw new Error('An error occurred while fetching data');
      }
    }

    return await response.json();
  } catch (error: any) {
    console.error('Error in fetchFromApi function: ', error);
    return {
      status: 'error',
      message: error?.message ?? 'An error occurred while fetching data',
    } as ApiError;
  }
};

export const getData = async <T>(
  endPoint: string
): Promise<ApiResponse<T> | ApiError> => {
  return await fetchFromApi(await url(endPoint), {
    headers: {
      ...header,
      Authorization: `Bearer ${await token()}`,
    },
    method: 'GET',
  });
};

export const updateData = async <T>(
  endPoint: string,
  data: any
): Promise<ApiResponse<T> | ApiError> => {
  return await fetchFromApi(await url(endPoint), {
    method: 'PATCH',
    headers: {
      ...header,
      Authorization: `Bearer ${await token()}`,
    },
    body: JSON.stringify({ data: { ...data } }),
  });
};

export const postData = async <T>(
  endPoint: string,
  data: any
): Promise<ApiResponse<T> | ApiError> => {
  return await fetchFromApi(await url(endPoint), {
    method: 'POST',
    headers: {
      ...header,
      Authorization: `Bearer ${await token()}`,
    },
    body: JSON.stringify({ data: { ...data } }),
  });
};

export const deleteData = async (
  endPoint: string
): Promise<ApiResponse<null> | ApiError> => {
  return await fetchFromApi(await url(endPoint), {
    method: 'DELETE',
    headers: {
      ...header,
      Authorization: `Bearer ${await token()}`,
    },
  });
};

Key Components Explained

Benefits of This Approach

Conclusion

Crafting an elegant API service for front-end applications is all about centralization, consistency, and leveraging the power of TypeScript. By following the principles outlined in this guide, developers can ensure a more maintainable, scalable, and user-friendly application.

Keep Exploring

8 Advanced JavaScript Array Methods for Efficient Data Manipulation

8 Advanced JavaScript Array Methods for Efficient Data Manipulation

While basic array methods like `push`, `pop`, or `slice` are widely known, there are several advanced methods that can supercharge our data manipulation capabilities.

7 Advanced Tips for Writing Cleaner and More Efficient JavaScript Code

7 Advanced Tips for Writing Cleaner and More Efficient JavaScript Code

Mastering the art of clean coding can significantly enhance performance, readability, and maintainability. In this article, we'll delve into seven advanced tips to elevate your JavaScript game.