{"id":26775766,"url":"https://github.com/jjlongoria/sf-rest-service-framework","last_synced_at":"2026-02-06T15:03:42.577Z","repository":{"id":110873631,"uuid":"431166551","full_name":"JJLongoria/sf-rest-service-framework","owner":"JJLongoria","description":"Framework to crete complete REST API's on Salesforce. ","archived":false,"fork":false,"pushed_at":"2022-04-10T14:05:19.000Z","size":29,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-06T00:05:22.301Z","etag":null,"topics":["apex","framework","rest-api","salesforce","salesforce-developers","sfdc","sfdx"],"latest_commit_sha":null,"homepage":"","language":"Apex","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JJLongoria.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-23T16:05:32.000Z","updated_at":"2023-05-06T14:00:23.000Z","dependencies_parsed_at":"2023-05-09T14:31:39.428Z","dependency_job_id":null,"html_url":"https://github.com/JJLongoria/sf-rest-service-framework","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/JJLongoria/sf-rest-service-framework","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JJLongoria%2Fsf-rest-service-framework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JJLongoria%2Fsf-rest-service-framework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JJLongoria%2Fsf-rest-service-framework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JJLongoria%2Fsf-rest-service-framework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JJLongoria","download_url":"https://codeload.github.com/JJLongoria/sf-rest-service-framework/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JJLongoria%2Fsf-rest-service-framework/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29165713,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-06T14:37:12.680Z","status":"ssl_error","status_checked_at":"2026-02-06T14:36:22.973Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apex","framework","rest-api","salesforce","salesforce-developers","sfdc","sfdx"],"created_at":"2025-03-29T03:28:50.468Z","updated_at":"2026-02-06T15:03:42.561Z","avatar_url":"https://github.com/JJLongoria.png","language":"Apex","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [**REST Service Framework**]()\n\nThe REST Service Framework are designed to create a complete API REST on Salesforce easy, with only one entry point (one *@RestResource* class) to **manage an entire standard API REST** easy to focus your efforts on routes definition and implementation.\n\nThis framework simplify manage and handle any API REST because use one (or a few) api entry points and not need to remember all entry point classes, only define your routes, the routing and implement your routes, let the framework make the rest.\n\nYou only need to extends the class `RestServiceRoute` on any Rest API Route to create your API. \n\n# [**Implementation**]()\n\n## [**Define Routes**]()\n\nFor example, we need to define the next routes on our API to work with Accounts, Opportunities and Contacts:\n\n- `api/v1.0/accounts/`\n- `api/v1.0/accounts/:accountId`\n\n\u003cbr/\u003e\n\n- `api/v1.0/accounts/:accountId/contacts`\n- `api/v1.0/accounts/:accountId/contacts/:contactId`\n- `api/v1.0/contacts`\n- `api/v1.0/contacts/:contactId`\n\n\u003cbr/\u003e\n\n- `api/v1.0/accounts/:accountId/opportunities`\n- `api/v1.0/accounts/:accountId/opportunities/:oppId`\n- `api/v1.0/opportunities`\n- `api/v1.0/opportunities/:oppId`\n\nThe Tree routes are the next:\n\n                api/v1.0\n            _______|_______\n            |      |      |\n            |   Accounts  |\n            |______|______|\n            |             |\n        Contacts     Opportunities           \n\n## [**Transform Routes to Classes**]()\n\nAccording the tree routes, we have an entry point `api/v1.0` and, at least, three main routes (classes), `Accounts`, `Contacts` and `Opportunidades`\n\nWe can transform the routes on classes like this: \n- `api/v1.0` =\u003e `APIEntryPointRoute`\n- `Accounts` =\u003e `AccountsRoute`\n- `Contacts` =\u003e `ContactsRoute`\n- `Opportunities` =\u003e `OpportunitiesRoute`\n\nWith this design, all main routes will be defined in `APIEntryPointRoute`, that is, `accounts`, `contacts` and `opportunities` routes and their controller classes (`AccountsRoute`, `ContactsRoute` and `OpportunitiesRoute`)\n\nDe esta forma la definición de las rutas principales de la API estarán en `APIEntryPointRoute` es decir, las rutas, `accounts`, `contacts` y `opportunities` estarán aqui definidas, así como su clase controladora respectivamente (`AccountsRoute`, `ContactsRoute` y `OpportunitiesRoute`)\n\nTherefore, the **`AccountsRoute`** class will manage the next routes\n\n- `api/v1.0/accounts/`\n- `api/v1.0/accounts/:accountId`\n\nand  **`ContactsRoute`**class will handle this routes\n\n- `api/v1.0/accounts/:accountId/contacts`\n- `api/v1.0/accounts/:accountId/contacts/:contactId`\n- `api/v1.0/contacts`\n- `api/v1.0/contacts/:contactId`\n\nand last, the **`OpportunitiesRoute`** class will be respond to all this routes\n\n- `api/v1.0/accounts/:accountId/opportunities`\n- `api/v1.0/accounts/:accountId/opportunities/:oppId`\n- `api/v1.0/opportunities`\n- `api/v1.0/opportunities/:oppId`\n\n--- \n\n## [**Define the API REST Entry Point `@RestResource`**]()\n\nWith this framework, we can define only one entry point to all our REST APIs on our Salesforce project, that is, onle one class with @RestResource tag, because the framework will handle and routing all requests.\n\n```java\n@RestResource(urlMapping='/api/*')\nglobal class APIRestEntryPoint{\n\n    private static void handleRequest(){\n      APIEntryPointRoute route = new APIEntryPointRoute();\n      route.execute();\n    }\n\n    @HttpGet\n    global static void handleGet() {\n        handleRequest();\n    }\n\n    @HttpPost\n    global static void handlePost() {\n        handleRequest();\n    }\n\n    @HttpPut\n    global static void handlePut() {\n        handleRequest();\n    }\n\n    @HttpDelete\n    global static void handleDelete() {\n        handleRequest();\n    }\n}\n```\nNo need implement any more on the entry point class, the entire implementation will be handle by `APIEntryPointRoute` as main router class.\n\n--- \n\n## [**Implement your Routes**]()\n\nTo implement the routes, we must create the classes `APIEntryPointRoute`, `AccountsRoute`, `ContactsRoute` and `OpportunitiesRoute` to handle REST Requests.\n\n### [**Implement API Entry Point Route**]()\n\n```java\npublic class APIEntryRoute extends RestServiceRoute {\n\n    // This class only need to implement (excep to handle other requests) the setupRoutes() method (inherited) to setup the API Routing\n\n    public override void setupRoutes() {\n        // use method addRoute() (inherited) to add any route to the API\n        addRoute('accounts', new AccountsRoute());\n        addRoute('contacts', new ContactsRoute());\n        addRoute('opportunities', new OpportunitiesRoute());\n    }\n}\n```\n\n--- \n\n### [**Implement Account Route (accounts)**]()\n\nThe Account route implementation example are:\n\n```java\npublic class AccountsRoute extends RestServiceRoute {\n\n    // Setup child routes\n    public override void setupRoutes() {\n        // use method addRoute() (inherited) to add any child route to account endpoint\n        addRoute('contacts', new ContactsRoute(getResourceId()));\n        addRoute('opportunities', new OpportunitiesRoute(getResourceId()));\n    }\n\n    public override Object doGet() {\n        if (!String.isEmpty(getResourceId())) {\n            // Handle request when have resourceId on URL\n            // Endpoint: api/v1.0/accounts/:accountId\n            Account acc = new Account();\n            // Implementation not shown\n            return acc;\n        } else if (containsQueryParameter('accountId')){\n            String recordId = getQueryParameter('accountId');\n            // Handle request when not has resourceId on URL but has resourceId on URL query parameter\n            // Endpoint: api/v1.0/accounts?accountId=XXXXXXX\n            Account acc = new Account();\n            // Implementation not shown\n            return acc;\n        } else {\n            // Handle request when not has resource Id on URL\n            // Endpoint: api/v1.0/accounts\n            List\u003cAccount\u003e acc = new List\u003cAccount\u003e();\n            // Implementation not shown\n            return acc;\n        }\n    }\n\n    // Include methods doPost(), doPut() or doDelete() to handle other requests\n}\n```\n\n--- \n\n### [**Implement Contacts Route (contacts)**]()\n\nTo implement the example contacts route, you can do the next:\n\n```java\npublic class ContactsRoute extends RestServiceRoute {\n\n    private String accountId;\n\n    public ContactsRoute(){\n\n    }\n\n    public ContactsRoute(String accountId){\n        this.accountId = accountId;\n    }\n\n    public override Object doGet() {\n        // To get the account Id if has on URL Query parameters\n        // Endpoint: api/v1.0/contacts?accountId=XXXXXX\n        if (this.accountId == null \u0026\u0026 containsQueryParameter('accountId')) {\n            this.accountId = getQueryParameter('accountId');\n        }\n\n        if (!String.isEmpty(getResourceId())) {\n            // Handle request when have resourceId on URL\n            // Endpoint: api/v1.0/accounts/:accountId/contacts/:contactId\n            // or Endpoint: api/v1.0/contacts/:contactId\n            Contact contact;\n            if (!String.isEmpty(this.accountId)){\n                // Handle request when has accountId\n                // Endpoint: api/v1.0/accounts/:accountId/contacts/:contactId\n                // or Endpoint: api/v1.0/contacts/:contactId/?accountId=XXXXXX\n                // Implementation not shown\n                return contact;\n            } else{\n                // Handle request when not has accountId\n                // Endpoint: api/v1.0/contacts/:contactId/\n                // Implementation not shown\n                return contact;\n            }\n            return contact;\n        } else if (!String.isEmpty(this.accountId)) {\n            // Handle request when not has resourceId but with acoountId like query parameter\n            // Endpoint: api/v1.0/contacts?accountId=XXXXXX\n            List\u003cContact\u003e contacts = List\u003cContact\u003e();\n            // Implementation not shown\n            return contacts;\n        } else {\n            // Handle request when not has resourceId or accountId\n            // Endpoint: api/v1.0/contacts\n            List\u003cContact\u003e contacts = List\u003cContact\u003e();\n            // Implementation not shown\n            return contacts;\n        }\n    }\n\n    // Include methods doPost(), doPut() or doDelete() to handle other requests\n}\n```\n\nThe `Opportunities` route implementation will be simillar like the other routes.\n\n--- \n\n### [**Handling Errors**]()\nThis Framework are designed to handle errors automatically when you use any class that extends from `RestService.RestException` class, that is, you can crete your custom exceptions to handling errors.\n\nBy default, the framework work with the estandard **JSONAPI 1.0** to return errors, but your can return any other object as response when you want. Into `RestServiceError` has all classes to handle errors with **JSONAPI 1.0**\n\nTo create custom exceptions:\n\n```java\npublic class MyCustomException extends RestServiceException {\n    // The object errorResponse will be serialized and included into the response body automatically when you throw any exception.\n    public MyCustomException(String message, Integer httpStatus, Object errorResponse) {\n        super(message, httpStatus, errorResponse);\n    }\n}\n```\n\nYou can throw or exception on any moment to handle errors and will be serialized and included into the response body automatically, including the httpStatus value into the response status code.\n\nIf you need to handle customized errors, can override the `handleException()` on any route to make your custom code error handling.\n\nThe actual method do the next:\n\n```java\nprotected virtual void handleException(Exception ex) {\n    if (ex instanceof RestService.RestServiceException) {\n        RestService.RestServiceException restErr = (RestService.RestServiceException)ex;\n        response.statusCode = restErr.status;\n        response.responseBody = (restErr.errorResponse != null) ? Blob.valueOf(JSON.serialize(restErr.errorResponse)) : response.responseBody;\n    } else {\n        throw ex;\n    }\n}\n```\n\nAnd we can override and make anything with it\n\n```java\npublic class ContactsRoute extends RestServiceRoute {\n\n    public override Object doGet() {\n        // Code\n    }\n\n    public override Object doPost() {\n        // Mode Code\n    }\n\n    ///.... other methods\n\n    // override method to handle errors customized\n    protected override void handleException(Exception ex) {\n        if (ex instanceof RestService.RestServiceException) {\n            // Handle framework exceptions\n        } else {\n            // Handle other any exception\n        }\n    }\n\n}\n```\n\n\u003e --- \n\u003e \n\u003e Only on special cases you will need to override the `handleException()` method\n\u003e\n\u003e ---\n\n--- \n\n### [**Return other Data types (`Content-Type`)**]()\nThe Rest Service Framework return by default JSON data (`Content-Type=application/json`), because serialize automatically the returned response object by the route methods to include on response body. If you need to return any other data type, can use the methods `setContentType('value')` and `setResponseBody()` (both inherited) to set the content type and body to your response. (You can use `this.response` like route property to modify anything of the response)\n\nExample:\n```java\npublic class CustomContentExampleRoute extends RestServiceRoute {\n    protected override Object doGet() {\n        setContentType('text/plain');\n        setResponseBody('This response is not a JSON Response');\n        // Return null to not include anything on body serialized as JSON (default behaviour)\n        return null;\n    }\n}\n```\n\n\u003e---\n\u003e\n\u003e If not return null, the response body setted with `setResponseBody()` method will be override.\n\u003e \n\u003e ---\n\n--- \n\n### [**Routes without ResourceId**]()\n\nSometimes, you need to implement the not standard REST routes like `/:RESOURCE_URI/:RESOURCE_ID`, for example, the next route:\n\n- `/api/v1/routeWithoutParam/otherRoute`\n\nThe `routeWithoutParam` route has not resourceId, in this case the next route are `otraRuta`. To implement this cases, you can do:\n\n```java\npublic class RouteWithoutResourceExampleRoute extends RestServiceRoute {\n\n    public RouteWithoutResourceExampleRoute(){\n        // On the constructor we call withoutResourceId() method to indicate to the framework that this route has not parameters\n        withoutResourceId();\n    }\n\n    // setup child routes\n    public override void setupRoutes() {\n        // use addRoute() method (inherited) to add any route\n        addRoute('otherRoute', new OtherRoute());\n    }\n\n    protected override Object doGet() {\n       // Code...\n    }\n}\n```\n\n--- \n\n### [**Expand Response**]()\nAn interesting framework function is the ability to expand the response object, that is, get all child routes data into one single response object. For example we can call the endpoint `api/v1.0/accounts/:accountId` and get the entire data from:\n\n- `api/v1.0/accounts/:accountId/contacts`\n- `api/v1.0/accounts/:accountId/opportunities`\n\nTo choose the expand response, call endpoints like this: `api/v1.0/accounts/:accountId?expand=true` \n```java\npublic class AccountRoute extends RestServiceRoute {\n\n    // Setu child routes\n    public override void setupRoutes() {\n        // use addRoute() method (inherited) to add any route\n        addRoute('contacts', new ContactsRoute(getResourceId()));\n        addRoute('opportunities', new OpportunitiesRoute(getResourceId()));\n    }\n\n    public override Object doGet() {\n        if (!String.isEmpty(getResourceId())) \n            Account acc = // Get account\n            // Use expandResponse() method to check if need to expand the response\n            if (expandResponse()) {\n                return expand(acc);\n            }\n            return acc;\n        }\n        //... collection\n    }\n}\n```\n\nThe expanded response will be like this:\n\n```json\n{\n    \"Id\": \"XXXXXXXXXXXX\",\n    \"Name\": \"Account Example Name\",\n    \"OtherAccountField\": \"Value\",\n    \"contacts\": [\n        {\n            \"Id\": \"YYYYYYYYYYYYYY\",\n            \"FirtName\": \"Contact 1 Firt Name\",\n            \"LastName\": \"Contact 1 Last Name\"\n        },\n        {\n            \"Id\": \"YYYYYYYYYYYYYY\",\n            \"FirtName\": \"Contact 2 Firt Name\",\n            \"LastName\": \"Contact 2 Last Name\"\n        }\n    ],\n    \"opportunities\": [\n        {\n            \"Id\": \"ZZZZZZZZZZZZZ\",\n            \"Name\": \"Opportunity Name\",\n            \"StageName\": \"Won\"\n        }\n    ]\n}\n```\n\n--- \n\n### [**Util inherited methods**]()\nThe `RestServiceRoute` class include to many inherited methods to use on any implement route to make easy implement APIs:\n\n- **`withoutResourceId()`**: To indicate that the route has not resource Id\n- **`getResourceId()`**: To get the resource id from URL \n- **`loadResource()`**: Method with several overloads to load a single resource (record) from database\n- **`loadResources()`**: Method with several overloads to load a resources list (records) from database\n- **`loadRelatedResource()`**: Method with several overloads to load a single related resource (record) from database\n- **`loadRelatedResources()`**: Method with several overloads to load related resources (records) from database\n- **`expandResponse()`**: Method to check if must return an expanded response\n- **`expand()`**: Method to expand the response with all endpoint data (with child endpoints data)\n- **`addRoute()`**: Method to add child routes to any route\n\nThe `RestServiceRoute` class also inherit from `RestService` class and contains other interesting methods to use on childs:\n\n- **`request`**: Property to get the Rest Request object\n- **`response`**: Property to get the Rest Response object\n- **`getQueryParameters()`**: Method to get the request query parameters map\n- **`containsQueryParameter()`**: Method to check if exists the selected request query parameter\n- **`getQueryParameter()`**: Method to get the selected request query paramter value\n- **`getRequestHeaders()`**: Method to get the request headers map\n- **`containsRequestHeader()`**: Method check if the request contains the selected header\n- **`getRequestHeader()`**: Method to get the request header selected value\n- **`addResponseHeader()`**: Method to add headers to response\n- **`setContentType()`**: To set the response content type (to use different from `application/json`)\n- **`setResponseBody()`**: To set the response body content (to use different from JSON responses)\n\n# Contributions\n\n- Code: Juan José Longoria López - Kanko (juanjoselongoria@gmail.com)\n- Inspired on REST Framework [**callawaycloud**](https://github.com/callawaycloud/apex-rest-route)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjjlongoria%2Fsf-rest-service-framework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjjlongoria%2Fsf-rest-service-framework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjjlongoria%2Fsf-rest-service-framework/lists"}