Salesforce Development: Integrations

Salesforce Developer Tutorial – How to create a custom REST Resource in Apex

4 min read

Subscribe on YouTube

Why This Is Useful

Salesforce already gives you a REST API for your objects, and for a lot of integrations that’s plenty. But sometimes you need an endpoint that does something specific: run business logic, return a shape that isn’t a raw SObject, or do several things in one call so the external system doesn’t have to make six.

That’s what a custom REST resource is for, and Apex makes it about as easy as it could be.

Code’s on GitHub.


The Code

Apex
@RestResource(UrlMapping='/Users/*')
global with sharing class Users_Rest_Resource
{
    @HttpGet
    global static List<User> getUsers()
    {
        RestRequest req = RestContext.request;
        System.debug(req.params);

        List<User> users = [SELECT Id, FirstName, LastName FROM User LIMIT 10];
        return users;
    }

    @HttpPost
    global static String insertUser(User newUser)
    {
        System.debug('This is a user ::: ' + newUser);
        insert newUser;
        return newUser.Id;
    }
}

That’s a complete, working REST API. Twenty lines.

Your endpoint is now:

Code
https://yourInstance.my.salesforce.com/services/apexrest/Users/

The Rules

A few non-negotiables here, and every one of them will produce a confusing error if you get it wrong.

The class must be global. Not public. As covered in the global keyword episode, this is one of the few places the platform genuinely forces your hand.

The methods must be global static. Both words, every time.

One method per HTTP verb. You get exactly one @HttpGet, one @HttpPost, one @HttpPut, one @HttpPatch, one @HttpDelete. You cannot have two GET methods with different signatures.

The UrlMapping is relative to /services/apexrest/. That trailing /* is a wildcard that lets you accept extra path segments.


Getting Data Out Of The Request

RestContext.request gives you everything about the incoming call:

Apex
RestRequest req = RestContext.request;

req.params;            //Map<String,String> of query string parameters
req.requestURI;        //the full path, useful with that /* wildcard
req.headers;           //Map<String,String> of headers
req.requestBody;       //Blob of the raw body
req.httpMethod;

That wildcard is how you build a proper resource path. A call to /services/apexrest/Users/005xx000001Sv1A lands in the same method, and you dig the Id out yourself:

Code
String userId = req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1);

The really nice bit is on POST. Notice insertUser takes a User parameter directly. Salesforce deserializes the JSON body into your method parameters automatically. Send this:

JSON
{
    "newUser": {
        "FirstName": "Matt",
        "LastName": "Gerry",
        "Username": "matt@example.com"
    }
}

…and it arrives as a populated User object. The JSON key has to match your parameter name exactly. This works with wrapper classes too, which is how you accept anything more complicated than a single record.


Controlling The Response

Return a value and Salesforce serializes it to JSON with a 200. Fine for the happy path. When you need real control:

Apex
@HttpGet
global static void getUsers()
{
    RestResponse res = RestContext.response;

    try
    {
        List<User> users = [SELECT Id, FirstName, LastName FROM User LIMIT 10];
        res.statusCode = 200;
        res.addHeader('Content-Type', 'application/json');
        res.responseBody = Blob.valueOf(JSON.serialize(users));
    }
    catch (Exception e)
    {
        res.statusCode = 500;
        res.responseBody = Blob.valueOf('{"error":"' + e.getMessage() + '"}');
    }
}

Note the return type becomes void when you’re writing the response yourself.

Please do this rather than letting exceptions escape. An unhandled Apex exception comes back as a 500 with a stack trace, which is unhelpful to the caller and quietly leaks your internals.


Security, Which Is Not Optional

You’re opening a door into your org, so:

Use with sharing. Notice it’s on the example class. Without it your endpoint runs in system context and ignores the calling user’s record access entirely. That’s almost never what you want, and it’s how data leaks happen.

Callers still need permissions. Apex REST respects object and field level security for the authenticated user, and they need access to the Apex class itself via profile or permission set.

Authentication is separate. Callers need a valid session or OAuth token. For server to server, that’s almost always the JWT Bearer flow, which I covered in the auth flows post.

Validate your inputs. That insert newUser; in the example is fine for a demo and I would not ship it. Whatever arrives from outside is untrusted.


Testing It

Apex
@IsTest
private class Users_Rest_ResourceTest
{
    @IsTest
    static void itReturnsUsers()
    {
        RestRequest req = new RestRequest();
        req.requestURI = '/services/apexrest/Users/';
        req.httpMethod = 'GET';
        RestContext.request = req;
        RestContext.response = new RestResponse();

        Test.startTest();
        List<User> users = Users_Rest_Resource.getUsers();
        Test.stopTest();

        System.assertNotEquals(null, users, 'Should have returned users');
    }
}

You build the RestContext by hand. No HTTP involved, which makes these very quick tests.

Full details in the Apex REST documentation. Now go build an endpoint.


Get Coding With The Force Merch!!

We now have a redbubble store setup so you can buy cool Coding With The Force merchandise! Please check it out! Every purchase goes to supporting the blog and YouTube channel.

Get Shirts Here!
Get Cups, Artwork, Coffee Cups, Bags, Masks and more here!


Check Out More Coding With The Force Stuff!

If you liked this post make sure to follow us on all our social media outlets to stay as up to date as possible with everything!

Youtube
Patreon
Github
Facebook
Twitter
Instagram


Salesforce Development Books I Recommend

Advanced Apex Programming
Salesforce Lightning Platform Enterprise Architecture
Mastering Salesforce DevOps

Good Non-SF Specific Development Books:

Clean Code
Clean Architecture