4

I have a requirement to write a REST class to fetch different set of resources that need to be accessed through different endpoints. Eg.

  • /services/apexrest/abc/contractors
  • /services/apexrest/abc/departments

Both the calls have to be POST calls as we need to send some details in request body. I read some QnA articles mentioning there can be only one @HttpPost method per class. (https://developer.salesforce.com/forums/?id=906F00000009DiDIAU)

But is there any alternate way to write a single apex class that can handle multiple POST calls ?

2 Answers 2

9

You're free to write your resource as:

    @RestResource(urlMapping='/abc/*')

Where * indicates a wildcard; this will match any resource that matches this wildcard pattern.

From here, you can then dispatch various methods:

    @HttpPost global static void doPost() {
      switch on RestContext.request.requestURI.substringAfterLast('/') {
        when 'contractors' { doContractorPost(); }
        when 'departments' { doDepartmentsPost(); }
        when else { doInvalidCall(); }
      }
    }

What you do from here is up to you. It's just code.

7

The URL that you use to access a custom apex rest service is determined by the @RestResource annotation, which can only be used at the class level.

So it makes sense for Salesforce to only allow a single @HttpPost method in a @RestResource annotated class. There would be no way to provide a more specific URL to be able to choose between two @HttpPost methods. The @HttpPost method also needs to be static, so that precludes using inner classes to help juggle things.

What you can do, however, is have your @HttpPost method act as a receptionist. Your @HttpPost method stands at the front door and interacts with all incoming requests. You can then have several other methods (or other classes) that your @HttpPost receptionist dispatches the incoming request to.

Something like

@RestResource(urlMapping='/my/endpoint/*')
global class MyRestAPI{
    // You could use an if/else if chain or a switch statement to take care of this,
    //   but I personally find using a Map to be more elegant
    private static Map<String, iRestPostHandler> resourceToHandler = new Map<String, iRestPostHandler>{
        '/my/endpoint/service1' => new Service1Handler(),
        '/my/endpoint/service2' => new Service2Handler()
    };

    @HttpPost
    global static void dispatch(){
        // @Http<X> annotated methods have access to the RestContext variable, which
        //   contains a RestRequest instance
        // The RestRequest contains the request URI, which will always start with the
        //   urlMapping defined in the @RestResource annotation

        if(resourceToHandler.containsKey(RestContext.request.requestURI)){
            // dispatch to the appropriate class
            resourceToHandler.get(RestContext.request.requestURI).handleRequest(RestContext.request, RestContext.response);
        }
    }

    public interface iRestPostHandler{
        // Have the interface take both the request (so you can handle the request body
        //   separately in each concrete class) and response (so that the dispatcher
        //   doesn't need to worry about handling output)
        void handleRequest(RestRequest req, RestResponse resp)
    }

    // Because we don't need to use the @HttpPost annotation (not like we could), we
    //   can make use of inner classes
    public class Service1Handler implements iRestPostHandler{
        public void handleRequest(RestRequest req, RestResponse resp){
            Map<String, Object> requestBodyDeserialized = JSON.deserializeUntyped(req.requestBody);
            //do work
        }
    }

    public class Service2Handler implements iRestPostHandler{
        public void handleRequest(RestRequest req, RestResponse resp){
            Map<String, Object> requestBodyDeserialized = JSON.deserializeUntyped(req.requestBody);

            //do different work
        }
    }
}
3
  • 1
    +1 We basically say the same thing, but in different ways. Always good to see variations. I'd caution against being too abstract though; at my last job, we had an API that consisted of about 12 different classes we had to read to understand what payloads were expected and what the response values were just for one endpoint. One can definitely overengineer once they go down this road.
    – sfdcfox
    Commented Sep 24, 2021 at 17:15
  • @sfdcfox That...actually sounds somewhat close to what I've been dealing with for the past 4 years. External system is chucking multiple (sometimes drastically different) payloads at a single API endpoint in Salesforce. Thankfully I was able to convince them to wrap the payloads in a header (for lack of better term) so I don't have to guess at how I need to deserialize the payload and stand a chance at being able to validate the payload.
    – Derek F
    Commented Sep 24, 2021 at 17:27
  • @sdfcfox and Derek - Thanks a lot for your answers, they were really helpful. There is however a new requirement and that is to pass everything that that we receive after '/abc/' to a SOQL query. How do I make sure that there is no security threat because of the value passed after the last '/' ? Commented Sep 25, 2021 at 8:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .