---
# System prepended metadata

title: REST APIs

---

# REST APIs

> TARGET AUDIENCE: This document is a required reading for developers. 

Here are the guidelines to implement REST API calls to invoke external business services.

# GETTING STARTED

## Client Operations

Supports only GET and POST. The remaining operations are not supported in the code such as PUT, PATCH, DELETE.

| METHOD | CONDITIONS                   | commons.ts Method signature                                                                                           |
| ------ | ---------------------------  | --------------------------------------------------------------------------------------------------------------------- |
| GET    | no query string             | `get: <TResponse,>(axios: AxiosInstance,  url: string) => Promise<TResponse>`                                          |
| GET    | query string                | `getWithParams: <TParams, TResponse>(axios: AxiosInstance, url: string, params: TParams) => Promise<TResponse>`        |
| POST   | body with optional response | `post: <TRequest = void, TResponse = void>(axios: AxiosInstance, url: string, body?: TRequest) => Promise<TResponse>` |                                                                                  |

> Remaining operations can be added following the same signature. using Axios in the closure and require any parameter to send over the network.

# GET DATA

## Implementing an API request

To implement a GET operation we will create a new file to hold the new operation method. If the operation name is `getProducts` then the file should be `getProducts.ts` by convention.

Constraints:

-   The operation method should receive an `AxiosInstance`. The instance is already configured with the base url, the operation method should only use a relative path.
-   This operation method should return a function. That function should carry the `AxiosIntance` within a closure (and any other `argument` that lives at Page level). The output function will be passed to the container. More detailes in [Page & Container](https://slite.com/api/public/notes/3yqZ6HqHQnJo11/redirect).
-   The operation method should leverage on an existing _client operation_. If a new client operation is required for the use case, a developer must to discuss it with the Architects team.

Let's write some code:

```typescript
import { type AxiosInstance } from "axios";
import { getWithParams } from "./common";

type ProductsRequest = {
  category?: string;
};

export type ProductsResponse = {
  data: [
    {
      name: string;
      price: number;
      categories: string[];
    }
  ];
};

export const getProducts =
  <TMappedOutput>(
    axios: AxiosInstance,
    mapper: (response: ProductsResponse) => TMappedOutput
  ) =>
  async (category?: string): Promise<TMappedOutput> => {
    return mapper(
      await getWithParams<ProductsRequest, ProductsResponse>(
        axios,
        "/products",
        {
          category,
        }
      )
    );
  };
```

## Using the API request at Page level

The operation method (i.e. `getProducts`) should be invoked ONLY at the Page level. The result value (which is a function) should ONLY be passed down the Container as a `prop`.

To get the AxiosInstance, we use the `useAxiosInstance()` function. That function returns an Axios instance with all the required settings already configured including the base url, authentication information and others required for the domain service.

> NOTE: This `useAxiosInstance()` function, return a singleton axios instance. Later on, we would need a way of getting the axios instance using different functions or an alternative approach in case we need to connect with multiple domain services or external services.

Example:

```typescript
import { getProducts, type ProductsResponse } from 'services';

const mapProducts = (r: ProductsResponse): Products[] = 
  r.data.map(a => a);

export const ProductsPage = () => {
  const axiosInstance = useAxiosInstance();

  return (
    <ProductsContainer
      getProducts={getProducts(axiosInstance, mapProducts)}
    />
  );
};
```

Taken into account that `mapProducts` is not implemented here. So far, 

**REMEMBER**: The Container is responsible for knowing when to invoke the function. This is valid for read and write operations. 


## Retrieving data at Container level

At the container level we should use `useQuery` hook (see [documentation](https://tanstack.com/query/v4/docs/react/reference/useQuery)). This provide us some important functionalities (such as error handling and loading indicators) .

> NOTE: This example is ONLY intended to show how to connect the operation method with the `useQuery`. The container uses different standard ways to mange other aspectsstate management in the example below is not the way we use in a real use case implementation.

Let's write an example container to understand how we use the `useQuery` hook.

```typescript
import { useState } from "react";
import { useQuery } from "react-query";

type Product = {
  name: string;
  price: number;
  categories: string[];
};

type ProductsContainerProps = {
  getProducts: () => Promise<Product[]>;
};

export const ProductsContainer = ({ getProducts }: ProductsContainerProps) => {
  const [products, setProducts] = useState<Product[]>([]);

  const productsQuery = useQuery({
    queryKey: "getProducts",
    queryFn: getProducts,
    onSuccess: (p) => {
      setProducts(p);
    },
  });

  return (
    <>
      {!!productsQuery.isLoading && <p>Loading...</p>}
      <ul>
        {products.map((product) => (
          <li key={product.name}>
            Product: {product.name} Price: {product.price.toFixed(2)}
          </li>
        ))}
      </ul>
    </>
  );
};
```

# MUTATE DATA

## Implement the API request


To implement a POST operation we will create a new file to hold the new operation method. If the operation name is `postCheckout` then the file should be `postCheckout.ts` by convention. Is basically the same approach used in `get` operations.

Constraints:

- The operation method should receive an `AxiosInstance`. The axios instance is already configured with the base url, the operation method should only use a relative path.
- This operation method should return a function. That function should carry the `AxiosInstance` within a closure (and any other argument that lives at Page level). The output function will be passed to the container.
- The operation method should leverage on an existing client operation. If a new client operation is required for the use case, a developer must to discuss it with the Architects team.

Let’s write some code:

```typescript
import { type AxiosInstance } from 'axios';
import { post } from './common';

export type FavoriteRequest = {
  product: string;
};

export const postFavorite =
  <TUnmappedInput>(
    axios: AxiosInstance,
    mapper: (input: TUnmappedInput) => FavoriteRequest,
  ) =>
  async (variables: TUnmappedInput): Promise<void> => {
    await post<FavoriteRequest>(axios, '/product/mark_as_favorite', mapper(variables));
  };
```

## Using the API request at Page level

Similarly to GET DATA section, the operation method should be invoked ONLY at the Page level. The result value (which is a function) should ONLY passed down the Container as a `prop`.

Get the `axiosInstance` as explained above. 

Let's update the first example page to see the differences: 

```typescript
import { getProducts, type ProductsResponse,  } from 'services';

const mapProducts = (r: ProductsResponse): Products[] => 
  r.data.map(a => a);

const mapFavorite = ({ product: string }): FavoriteRequest => 
  ({ product });  

export const ProductsPage = () => {
  const axiosInstance = useAxiosInstance();

  return (
    <ProductsContainer
      getProducts={getProducts(axiosInstance, mapProducts)}
      postFavorite={postFavorite(axiosInstance, mapFavorite)}
    />
  );
};
```

**REMINDER**: The Container is responsible for knowing WHEN to invoke the function. This is valid for read and write operations. 

## Mutation data at Container level

Mutating data is a bit different than retrieving data at the container level. First, we leverage on `useMutation` hook (see [documentation](https://tanstack.com/query/v4/docs/react/reference/useMutation)). As well as in retrieve operations, this provide us some important functionalities (such as error handling and loading indicators) .

Let's update the first example container to understand how we use the `useMutation` hook.

```typescript
import { useState } from "react";
import { useQuery, useMutation } from "react-query";

type Product = {
  name: string;
  price: number;
  categories: string[];
};

type ProductsContainerProps = {
  getProducts: () => Promise<Product[]>;
  postFavorite: (variables: { product: string; }) => Promise<void>;
};

export const ProductsContainer = ({ getProducts, postFavorite }: ProductsContainerProps) => {
  const [products, setProducts] = useState<Product[]>([]);

  const productsQuery = useQuery({
    queryKey: "getProducts",
    queryFn: getProducts,
    onSuccess: (p) => {
      setProducts(p);
    },
  });
    
  const favoriteMutation = useMutation('postFavorite', postFavorite);
  
  const markAsFavorite = (product: string) => favoriteMutation.mutate({ product });
  return (
    <>
      {!!productsQuery.isLoading && <p>Loading...</p>}
      <ul>
        {products.map((product) => (
          <li key={product.name}>
            Product: {product.name} Price: {product.price.toFixed(2)}
            <button onClick={() => markAsFavorite(product.name)}>Mark As Favorite</button>
          </li>
        ))}
      </ul>
    </>
  );
};
```

