Create a REST API with SFDX, Part 2

In part 1 we setup our scratch org and created our custom object. In this post we will build out the API.

First, create a new class in Visual Studio Code called HouseService. From the command palette in VS Code select SFDX: Create Apex Class and enter the name of the class.

A few things to note about the class definition:

  • @RestResource identifies our class as a REST endpoint.
  • urlMapping='/House/*' sets the route for our REST endpoint. Any request to https://[instance].lightning.force.com/services/apexrest/House/* will be routed to this class. The * is a wildcard and means anything can come after /House/ and the request will still be routed to this class.
  • REST endpoints must be global
  • The four comments represent the four methods we will create to implement our API.

HTTP Methods

We are going to implement the following HTTP Methods. For more information about HTTP Methods check out this guide from Mozilla.

  • POST: Create a new House record
  • PATCH: Update an existing House record
  • GET: Get a House record by its Id
  • DELETE: Delete a House record by its Id

Return Values

HTTP methods return a response object and that object has two properties that are important to our response to the caller:

  • Status Code: Numeric value to indicate the outcome of the request. It is used by many http client libraries to determine if a call succeeded or failed.
  • Response Body: The details of our response. For example, if a method returns a House object this would be a JSON serialized house object. If we’re returning an error code this could also be a detailed error message.

In Apex we can set these using the RestContext.response object:

If we set these two properties our Apex method can return void but our API will still return the appropriate status code and response body.

I see some REST APIs implemented in Apex that do not explicitly set the status code and response body. Sometimes these methods return a custom object that includes a boolean property to indicate if the method succeeded or failed and lets Salesforce set a default status code which is almost always 200. This approach works but it violates standard REST conventions. In my opinion, that makes the API more difficult to work with. A developer calling that API may have to do extra work to determine if a call succeeded or failed instead of letting their HTTP client library do it for them based on the status code. REST is one of those topics where people can get into heated debates about what is and isn’t “RESTful” and that is not my intention. I try to keep it simple and follow these three rules to make my APIs “RESTful” enough.

  1. Use the appropriate HTTP verb (POST, PATCH, GET, etc)
  2. Return a meaningful status code
  3. Return a meaningful response body. For example, return the data the caller expects if the method succeeds. Return an error message if the method fails.

With that in mind, each of our HTTP methods will have a return type of void. We will create a response object in the method and explicitly set the status code for all of the success and failure conditions we handle. The status code will be set to 500 Internal Server Error for any unhandled/generic exceptions. We will also explicitly set the response body, except in the few cases where it’s not necessary. Beyond that, we can leave the heated debates to the RESTifarians.

POST Method

We will name the POST method createHouse and it will satisfy the following requirements:

  1. It takes a JSON serialized House object in the request body.
  2. If it successfully creates a new House object it returns status code 201 Created and returns the Id of the new House object in the response body.
  3. If it is unable to create a new House object it returns status code 400 Bad Request.
  4. Any other exceptions will return status code 500 Internal Server Error.

The request body looks like this:

You can also remove the type attribute and it will work. The code that follows satisfies the requirements we defined above.

Line 1: @HttpPost identifies this as the method that handles HTTP POST.
Line 2: @Http methods must be global static.
Line 3: We use the RestContext.response object to set the status code and response body that is returned to the caller.
Lines 4, 6: Get the request body and attempt to deserialize it into a House object.
Line 7: Insert a new House into the database.
Line 8: If the insert is successful set the status code to 201 Created.
Line 9: Set the response body to the Id of the new House object.
Lines 11-12: If the insert fails set the status code to 400 Bad Request and set the response body to the exception message.
Lines 14-15: Handle all other exceptions.

PATCH Method

The PATCH method is updateExistingHouse and it has the following requirements:

  1. The URL is /House/{Id} where {Id} is the Id of the object to update.
  2. Accepts a JSON serialized House object in the request body. This is the updated version of the House object.
  3. If the House object is successfully updated it returns status code 204 No Content.
  4. If it is unable to update the House object it returns status code 400 Bad Request.
  5. If there is no House object that matches the {Id} parameter it returns status code 404 Not Found.
  6. Any other exceptions will return status code 500 Internal Server Error.

Line 1: @HttpPatch identifies this as the method that handles HTTP PATCH.
Lines 5: Parse the URL to get the Id from /House/{Id}.
Lines 6-10: Get the request body and attempt to deserialize it into a House object.
Lines 11: Get the existing House object from the database.
Lines 12-18: Update the existing House object.
Line 20: Set the status code to 204 No Content.
Lines 22-23: Set the status code to 400 Bad Request if there is a DML exception.
Lines 25-26: Set the status code to 404 Not Found if the {Id} parameter doesn’t match a House object in the database.
Lines 28-29: Set the status code to 500 Internal Server Error for all other errors.

GET Method

The GET method is getHouseById and its requirements are:

  • The URL is /House/{Id} where {Id} is the id of the object to fetch.
  • If a House object is retrieved from the database it returns status code 200 OK and returns the House object in the response body.
  • If it is unable to find a House object with the specified Id it returns status code 404 Not Found.
  • If there is any other exception it returns status code 500 Internal Server Error.

Line 1: @HttpGet identifies this as the method that handles HTTP GET requests.
Lines 6: Get the Id from the URI. If this fails we’ll catch a generic exception and return 500 on lines 27-28.
Lines 7-20: Fetch the object from the database. If this query returns no results it will throw a Query exception which we catch and return 404 on lines 24-25.
Lines 21-22: Set the status code to 200 and set the response body to the House object.

DELETE Method

The DELETE method is deleteHouseById and it’s requirements are:

  • The URL is /House/{Id} where {Id} is the id of the object to delete.
  • If it succeeds it returns status code 204 No Content.
  • If it is unable to find a House with the specified Id it returns status code 404 Not Found.
  • If there is any other exception it returns status code 500 Internal Server Error.

The code for the DELETE method is very similar to the GET method except instead of retrieving the House object from the database and returning it to the user we delete it. Also, in the GET method we return 404 Not Found if we can’t find the House object. In the DELETE method we return 204 No Content. DELETE calls are idempotent so if you try to delete something more than once the end result is the same — 204 No Content.

That’s it! If you are following along in your own project you should be able to push this code to your scratch org and test it.

In part 3 we will unit test the api.

Leave a Comment