RESTful Api client with only GET + POST
Tags: dotnet, microservices, typescript, api
Published: 2021-11-03 17:31:56

Currently, I work on a project based on micro-services and web application. The product is developped with these technologies :
- Micro-service : API in ASP.NET Core
- Web Application : React and TypeScript (with axios)

During the production launch on the client servers, a few problems with different configurations occurred. In fact, on almost all the clients, all the requests returned a result

```
200 OK
```

On one particular server, requests with RESTful verbs like `PUT` ,`PATCH` and `DELETE` were returning with the following status:

```
405 Method not allowed
```

Indeed, some of them blocked any HTTP request not using the simple `GET` + `POST` verbs, for security reasons.

## Configuration

The product is hosted on a dedicated server at a service provider. This is behind a Nginx reverse proxy, which is global to all the provider’s servers. From then on, two solutions were available :

- Allow `PUT`, `PATCH`, `DELETE` requests on the reverse proxy
- Adapt the micro-services with `X-Method-Override` header

As the first alternative is not possible, as the change may alter the security of the products hosted by the provider, the second alternative is chosen.

## Typescript HTTP Client

The webapp is based on React and TypeScript with `axios` library. The purpose is to define a custom API Client based on `axios` and set the `X-Method-Override` header.

### Client interface
Below appears the interface of our custom API client :

```ts
interface ApiClient {
  instance(): ApiClient;
  get<T>(url: string): Promise<T>;
  post<T>(url: string, body: any): Promise<T>;
  put<T>(url: string, body: any): Promise<T>;
  patch<T>(url: string, body: any): Promise<T>;
  delete<T>(url: string): Promise<T>;
}
```

With this interface, the client will be able to use the 5 main HTTP verbs : `GET`, `POST`, `PUT`, `PATCH` and `DELETE`.
The client is designed with Singleton pattern (you can notice the instance method !).

### Client implementation

```ts
import Axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";

type HttpVerb = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";

class ApiClient {
  private static singleton: ApiClient;
  private readonly instanceAxios: AxiosInstance;
  private URL: string = "https://localhost:44300/";

  private constructor() {
    this.instanceAxios = Axios.create({
      baseURL: `${this.URL}api/`,
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
    });
  }

  public static get instance(): ApiClient {
    if (!ApiClient.singleton) {
      this.singleton = new ApiClient();
    }

    return ApiClient.singleton;
  }

  private async request<T>(method: HttpVerb, url: string, body?: any): Promise<T> {
    const requestConfig: AxiosRequestConfig = {
      method: method === "GET" ? "GET" : "POST",
      url: url,
      headers: {/*... */},
    };
    // Use X-HTTP-Method-Override to use all HTTP verbs
    // Verbs will be encapsulated in POST Header
    if (method !== "GET" && method !== "POST")
      requestConfig.headers["X-HTTP-Method-Override"] = method;

    if (body) {
      requestConfig.data = body;
    }

    try {
      const response: AxiosResponse = await this.instanceAxios(requestConfig);
      return response.data as T;
    } catch (error) {
      throw new Error(error.response.data);
    }
  }

  public async get<T>(url: string): Promise<T> {
    try {
      const response: AxiosResponse = await this.instanceAxios.get(url);
      return response.data as T;
    } catch (error) {
      throw new Error(/*..*/);
    }
  }

  public async post<T>(url: string, body: any): Promise<T> {
    return this.request("POST", url, body);
  }

  public async put<T>(url: string, body: any): Promise<T> {
    return this.request("PUT", url, body);
  }

  public async patch<T>(url: string, body?: any, ): Promise<T> {
    return this.request("PATCH", url, body);
  }

  public delete<T>(url: string): Promise<T> {
    return this.request("DELETE", url);
  }
}

export default ApiClient.instance;
```

Now, it is easy to use this client on all our other *.ts files :

```ts
import { ApiClient} from "@services/ApiClient"
var data1 = await ApiClient.get("/myUrl");
var patchData = {
    "property": "value"
};
// The PATCH method will add a X-HTTP-Method-Override header
var responseToPatch = await ApiClient.patch("/myUrl2", patchData);
```

## Micro-services in ASP.NET Core

Now, it is important to tell to ASP.NET Core to check the `X-HTTP-Method-Override` header. It is easily done with the `IApplicationBuilder.UseHttpMethodOverride` method !

```cs
// All using...

namespace My.Awesome.Namespace.Api
{ 
    public class Startup
    {
        // ...
        public void ConfigureServices(IServiceCollection services)
        {
            // ...
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            // ...
            // ASP.NET Core API will handle X-HTTP-Method-Override header !
            app.UseHttpMethodOverride();
            
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}
```

## Conclusion

If you encounter servers that do not allow HTTP verbs to comply with REST principles, don’t worry. It is possible to encapsulate REST verbs in the `X-HTTP-Method-Override` header, as demonstrated in this article !

## Ressources
- Illustration : Business vector created by fullvector — www.freepik.com