REST Resource Code Examples
Example 1: Versioned GET Endpoint with Error Contract
@RestResource(urlMapping='/accounts/v1/*')
global with sharing class AccountResourceV1 {
global class ApiError {
public String code;
public String message;
}
global class AccountDto {
public Id id;
public String name;
public String industry;
}
@HttpGet
global static AccountDto getById() {
RestRequest req = RestContext.request;
String recordId = req.requestURI.substringAfterLast('/');
if (String.isBlank(recordId)) {
throwApiError(400, 'INVALID_REQUEST', 'Account Id is required.');
}
Account a = [
SELECT Id, Name, Industry
FROM Account
WHERE Id = :recordId
LIMIT 1
];
AccountDto dto = new AccountDto();
dto.id = a.Id;
dto.name = a.Name;
dto.industry = a.Industry;
return dto;
}
private static void throwApiError(Integer statusCode, String code, String message) {
RestContext.response.statusCode = statusCode;
ApiError e = new ApiError();
e.code = code;
e.message = message;
RestContext.response.responseBody = Blob.valueOf(JSON.serialize(e));
throw new AuraHandledException(message);
}
}
Example 2: Bulk POST Upsert Endpoint
@RestResource(urlMapping='/contacts/v1/upsert')
global with sharing class ContactUpsertResourceV1 {
global class UpsertRequest {
public String externalId;
public String lastName;
public String email;
}
global class UpsertResultDto {
public String externalId;
public Boolean success;
public String error;
}
@HttpPost
global static List<UpsertResultDto> upsertContacts() {
List<UpsertRequest> payload =
(List<UpsertRequest>) JSON.deserialize(
RestContext.request.requestBody.toString(),
List<UpsertRequest>.class
);
List<Contact> contacts = new List<Contact>();
for (UpsertRequest row : payload) {
contacts.add(new Contact(
External_Id__c = row.externalId,
LastName = row.lastName,
Email = row.email
));
}
Database.UpsertResult[] results = Database.upsert(
contacts,
Contact.External_Id__c,
false
);
List<UpsertResultDto> response = new List<UpsertResultDto>();
for (Integer i = 0; i < results.size(); i++) {
UpsertResultDto dto = new UpsertResultDto();
dto.externalId = payload[i].externalId;
dto.success = results[i].isSuccess();
dto.error = dto.success ? null : results[i].getErrors()[0].getMessage();
response.add(dto);
}
return response;
}
}
Example 3: Basic Test Pattern
@IsTest
private class ContactUpsertResourceV1Test {
@IsTest
static void upsertContacts_returnsPerRowStatus() {
RestRequest req = new RestRequest();
req.requestUri = '/services/apexrest/contacts/v1/upsert';
req.httpMethod = 'POST';
req.addHeader('Content-Type', 'application/json');
req.requestBody = Blob.valueOf('[{"externalId":"EXT-001","lastName":"Test"}]');
RestContext.request = req;
RestContext.response = new RestResponse();
List<ContactUpsertResourceV1.UpsertResultDto> out = ContactUpsertResourceV1.upsertContacts();
System.assertEquals(1, out.size());
System.assertEquals(true, out[0].success);
}
}
Implementation Notes
- Version endpoints (
/v1) to avoid breaking clients. - Use Named Credentials for outbound callbacks from handlers.
- Return consistent error contracts so clients can retry safely.
- Keep handlers thin; move business logic to service classes.