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