# 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> </> ); }; ```