DEV Community

Lucas Fridez
Lucas Fridez

Posted on

RESTful Api client with only GET + POST

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
Enter fullscreen mode Exit fullscreen mode

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

405 Method not allowed
Enter fullscreen mode Exit fullscreen mode

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 :

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>;
}
Enter fullscreen mode Exit fullscreen mode

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

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;
Enter fullscreen mode Exit fullscreen mode

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

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);
Enter fullscreen mode Exit fullscreen mode

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 !

// 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();
            });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)