# Working Effectively with Libraries (facade pattern)

# Introduction

When building web apps, making HTTP requests is something you'll do a lot. Whether you're using `fetch`, Axios, or another library, having a simple way to handle these requests can make your code easier to manage.

# The Problem

Here's how API calls often look in many codebases:

```typescript
// Scattered throughout your app...
async function getUser(id: string) {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) throw new Error("Failed to fetch user");
  return response.json();
}

async function updateUser(id: string, data: UserData) {
  const response = await fetch(`/api/users/${id}`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });
  if (!response.ok) throw new Error("Failed to update user");
  return response.json();
}
```

Issues:

* Error handling is repeated
    
* Changing from `fetch` to Axios needs updates everywhere
    
* Headers and request settings are spread out
    

# A Better Approach

Let's create a simple request function that handles these concerns:

```typescript
type RequestConfig = {
  method?: "GET" | "POST" | "PUT" | "DELETE";
  headers?: Record<string, string>;
  data?: unknown;
};

async function request<TResponse>(
  url: string,
  config: RequestConfig = {}
): Promise<TResponse> {
  const { method = "GET", headers = {}, data } = config;

  const response = await fetch(url, {
    method,
    headers: {
      "Content-Type": "application/json",
      ...headers,
    },
    ...(data && { body: JSON.stringify(data) }),
  });

  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`);
  }

  return response.json();
}

// Usage
interface User {
  id: string;
  name: string;
}

async function getUser(id: string) {
  return request<User>(`/api/users/${id}`);
}

async function updateUser(id: string, data: Partial<User>) {
  return request<User>(`/api/users/${id}`, {
    method: "PUT",
    data,
  });
}
```

# Want to Use Axios Instead?

Just swap the implementation, keeping the same interface:

```typescript
import axios from "axios";

async function request<TResponse>(
  url: string,
  config: RequestConfig = {}
): Promise<TResponse> {
  const { method = "GET", headers, data } = config;

  const response = await axios({
    url,
    method,
    headers,
    data,
  });

  return response.data;
}
```

Your application code doesn't need to change at all!

# Going Further

Need to handle authentication? Add retries? Just enhance your request function:

```typescript
async function request<TResponse>(
  url: string,
  config: RequestConfig = {}
): Promise<TResponse> {
  const { method = "GET", headers = {}, data } = config;

  // Add auth token
  const token = getAuthToken();
  if (token) {
    headers.Authorization = `Bearer ${token}`;
  }

  try {
    const response = await fetch(url, {
      method,
      headers: {
        "Content-Type": "application/json",
        ...headers,
      },
      ...(data && { body: JSON.stringify(data) }),
    });

    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }

    return response.json();
  } catch (error) {
    // Handle retries, logging, etc.
    throw error;
  }
}
```

The great thing about this approach is that all HTTP-related tasks are managed in one spot. Your application code remains clean and focused on the main business logic.

# Remember

**Good abstractions don't try to handle every possible case.**

They handle the usual cases well and are flexible enough to extend when necessary.
