{"id":18612431,"url":"https://github.com/avivcarmis/trafficante","last_synced_at":"2026-05-08T14:15:59.954Z","repository":{"id":57732509,"uuid":"91239522","full_name":"avivcarmis/trafficante","owner":"avivcarmis","description":"Ridiculously Simple Strongly Typed API Server with Spring Boot and Swagger","archived":false,"fork":false,"pushed_at":"2017-05-18T14:38:34.000Z","size":3201,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-12-27T02:14:03.970Z","etag":null,"topics":["api","server","spring-boot","spring-mvc","swagger"],"latest_commit_sha":null,"homepage":"","language":"Java","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/avivcarmis.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}},"created_at":"2017-05-14T11:43:37.000Z","updated_at":"2018-09-01T08:50:29.000Z","dependencies_parsed_at":"2022-08-24T01:11:10.053Z","dependency_job_id":null,"html_url":"https://github.com/avivcarmis/trafficante","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avivcarmis%2Ftrafficante","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avivcarmis%2Ftrafficante/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avivcarmis%2Ftrafficante/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avivcarmis%2Ftrafficante/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avivcarmis","download_url":"https://codeload.github.com/avivcarmis/trafficante/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239402847,"owners_count":19632466,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["api","server","spring-boot","spring-mvc","swagger"],"created_at":"2024-11-07T03:17:10.092Z","updated_at":"2025-10-06T14:51:14.634Z","avatar_url":"https://github.com/avivcarmis.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Trafficante : Strongly Typed API Server\n[![Trafficante](https://avivcarmis.github.io/trafficante/trafficante-logo.jpg \"Trafficante\")](https://github.com/avivcarmis/trafficante \"Trafficante\")\n\n------------\n\n**Trafficante** library introduces a very simple and intuitive way to construct a strongly typed server with spring boot and swagger within seconds.\nTrafficante requires JDK 1.8 or higher.\n\n### Latest Release\n------------\nThe most recent release is Trafficante 1.0.2, released May, 2017.\n\nTo add a dependency on Trafficante Library using Maven, use the following:\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eio.github.avivcarmis\u003c/groupId\u003e\n    \u003cartifactId\u003etrafficante\u003c/artifactId\u003e\n    \u003cversion\u003e1.0.2\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nTo add a dependency on Trafficante Library using Gradle, use the following:\n```groovy\ncompile 'io.github.avivcarmis:trafficante:1.0.2'\n```\n\n### Getting Started\n------------\nTrafficante library divides the server into different `Endpoints`. Each endpoint is responsible to handle requests with certain HTTP method and path (i.e. `POST /get_users`), and defines strongly typed request and response entities. Let's implement some example endpoint:\n\n```java\npublic class GetUserById extends BasicEndpoint\u003cGetUserById.Request, GetUserById.Response, GetUserById.Response\u003e {\n\n    public GetUserById() {\n        super(RequestMethod.GET, true);\n    }\n\n    @Override\n    protected Response handle(Request request) throws APIException {\n        Integer userId = request.userId;\n        User user = // ...acquire user using userId\n        return new Response(user.getFirstName(), user.getLastName(), user,getWebsiteURL());\n    }\n\n    @Override\n    protected Response wrapResponse(Response response) {\n        return response;\n    }\n\n    @Override\n    protected Response wrapFailure(Throwable throwable) {\n        return new Response(null, null, null);\n    }\n\n    public static class Request {\n\n        private Integer userId;\n\n    }\n\n    public static class Response {\n\n        private final String firstName;\n\n        private final String lastName;\n\n        private final String websiteURL;\n\n        public Response(String firstName, String lastName, String websiteURL) {\n            this.firstName = firstName;\n            this.lastName = lastName;\n            this.websiteURL = websiteURL;\n        }\n\n    }\n\n}\n```\n\nSo what's going on here? Our endpoint class extends abstract `BasicEndpoint`, declares it's request and response entity to be `Request` and `Response` classes (just ignore the third generic type for the moment). The request and response classes are nestedly defined, but they don't need to be and it's entirely up to your own coding style.\nThen it declares the HTTP method it expects and whether or not to log the traffic in the super constructor.\nThe path of the endpoint is automatically derived from the class name, so the above endpoint will be registered to handle calls to `GET /getUserById` or `GET /get_user_by_id` or any other custom naming strategy you initially define (more on naming strategies in a moment).\nThen it defines it's business logic, what actually the endpoint class does to process a request and produce a response, using the implemented `handle` method.\nAnd let's also ignore those `wrapResponse` and `wrapFailure` for the moment.\n\nThat's it! We have our first endpoint ready!\nNow we need to create a main class and start a new Trafficante server:\n\n```java\npublic class Main {\n\n    public static void main(String[] args) {\n        Trafficante.start(\n            \"com.example\",                      // base package name containing all my endpoint classes\n            ServerNamingStrategy.SNAKE_CASE,    // property naming strategy to be used server-wide\n            \"0.0.0.0\",                          // host name to be registered - \"0.0.0.0\" to allow all\n            8080,                               // port to be used\n            true,                               // whether or not to enable swagger - should typically be `false` in production environments\n            true,                               // whether or not to enable JMX support\n            args                                // program arguments - may be null\n        );\n    }\n\n}\n```\nNow our server is up and running.\n\n### Let's Dive Deeper\n------------\nSo we've seen the minimal code required to construct a Trafficante server, now let's explore the recommended setting.\nEndpoint classes support response wrapping, to allow client a easy parsing of the response in case of either success and failure, and across the entire server.\nLet's, for example, consider this JSON structure to be responded for each and every server request:\n```json\n{\n    \"success\": true,\n    \"result\": {},\n    \"error\": null\n}\n```\nwhere result may be any response entity, and in case of failure:\n```json\n{\n    \"success\": false,\n    \"result\": null,\n    \"error\": \"string describing the error\"\n}\n```\nTo achieve this we need to follow a few simple steps:\n1. Declare response wrapping entity.\n2. Define a standard endpoint class.\n3. Define additional error handler.\n\nSo first, let's declare our wrapping entity. The Java version of the JSON above can be achieved using:\n\n```java\npublic class APIResponse\u003cT\u003e {\n\n    private final boolean success;\n\n    private final T result;\n\n    private final String error;\n\n    private APIResponse(boolean success, T result, String error) {\n        this.success = success;\n        this.result = result;\n        this.error = error;\n    }\n\n    public static \u003cT\u003e APIResponse\u003cT\u003e success(T result) {\n        return new APIResponse\u003c\u003e(true, result, null);\n    }\n\n    public static \u003cT\u003e APIResponse\u003cT\u003e failure(Throwable t) {\n        return new APIResponse\u003c\u003e(false, null, t.getMessage());\n    }\n\n}\n```\n\nThis is simple, next, we can easily define a standard abstract endpoint class to be used across the entire server:\n\n```java\nabstract public class Endpoint\u003cREQ, RES\u003e extends BasicEndpoint\u003cREQ, RES, APIResponse\u003cRES\u003e\u003e {\n\n    public Endpoint(RequestMethod httpMethod, boolean enableFlowLogging) {\n        super(httpMethod, enableFlowLogging);\n    }\n\n    @Override\n    protected APIResponse\u003cRES\u003e wrapResponse(RES response) {\n        return APIResponse.success(response);\n    }\n\n    @Override\n    protected APIResponse\u003cRES\u003e wrapFailure(Throwable t) {\n        return APIResponse.failure(t);\n    }\n\n}\n```\n\nSo as you now can see, the third generic type of the `BasicEndpoint` class expects the type of the response wrapper. Since we previously didn't wrap our response, we just passed the same type twice. In the current case we wire the `APIResponse` class to be generated both on failure and on success of the endpoint.\n\nLastly, we want to be able to control the response of failure that don't get to reach a specific endpoint class, like 404, or 405 HTTP errors for example. To this end, we need to inherit `BasicErrorHandler` class:\n\n```java\npublic class ErrorHandler extends BasicErrorHandler\u003cAPIResponse\u003c?\u003e\u003e {\n\n    @Override\n    protected APIResponse\u003c?\u003e wrapFailure(Throwable t) {\n        return APIResponse.failure(t);\n    }\n\n}\n```\n\nNow let's get back to our original endpoint example, and re-implement it using our newly create `Endpoint` class:\n\n```java\npublic class GetUserById extends Endpoint\u003cGetUserById.Request, GetUserById.Response\u003e {\n\n    public GetUserById() {\n        super(RequestMethod.GET, true);\n    }\n\n    @Override\n    protected Response handle(Request request) throws APIException {\n        Integer userId = request.userId;\n        User user = // ...acquire user using userId\n        return new Response(user.getFirstName(), user.getLastName(), user,getWebsiteURL());\n    }\n\n    public static class Request {\n\n        private Integer userId;\n\n    }\n\n    public static class Response {\n\n        private final String firstName;\n\n        private final String lastName;\n\n        private final String websiteURL;\n\n        public Response(String firstName, String lastName, String websiteURL) {\n            this.firstName = firstName;\n            this.lastName = lastName;\n            this.websiteURL = websiteURL;\n        }\n\n    }\n\n}\n```\n\nThat's a bit cleaner, but more importantly, now our server responds with the same object structure to each request, allowing the client for easy and intuitive consumption.\n\nIn order to read and write HTTP headers and status codes, you may call the thread-safe, static methods:\n- `BasicEndpoint.requestHeader(String key)` return the value of the given request header or `null` if not found.\n- `BasicEndpoint.responseHeader(String key, String value)` which writes a response header.\n- `BasicEndpoint.responseStatusCode(HttpStatus status)` which alters the response code.\n\nThis methods may be called from anywhere in the code.\n\n### Validation, Naming and Further Customization\n----\nIn real life, we would want to validate the `GetUserById` request. No need to manually trigger such a request. When the endpoint `handle` method is invoked the request object is already validated.\nIn the above case we did not specify any validation and so any request will be valid. Trafficante supports a simple  two types of validation approach to cover all your needs:\n1. Required fields should be annotated with an `@Required` annotation.\n2. Any additional validation is defined using the `Validatable` interface.\nLet's, for example, consider a valid request object to specify a `userId` with a positive sign integer. Then our `Request` class should be altered this way:\n\n```java\n    public static class Request implements Validatable {\n\n        @Required\n        private Integer userId;\n\n        @Override\n        public void validate() throws BadRequestException {\n            if (userId \u003c 0) {\n                throw new BadRequestException(\"user id must be \u003e= 0\");\n            }\n        }\n\n    }\n```\n\nThe term `Naming` in the context of Trafficante refers to two concepts - method of mapping an endpoint class to path, and the naming of properties in the request and the response.\nBy default, a server-wide naming strategy is picked up, and then all the magic happens. For the above example using snake case naming convention - a valid request may be `GET /get_user_by_id?user_id=1`, and it's response may be:\n```json\n{\n    \"success\": true,\n    \"result\": {\n        \"first_name\": \"john\",\n        \"last_name\": \"doe\",\n        \"website_url\": \"example.com\"\n    },\n    \"error\": null\n}\n```\n\nThe above may be achieved using `ServerNamingStrategy.SNAKE_CASE` property naming when starting a new Trafficante server like in the example above. Trafficante offers 3 basic out of the box strategies to cover the common cases:\n1. `ServerNamingStrategy.SNAKE_CASE` which translate naming from camel case to snake case.\n2. `ServerNamingStrategy.CAMEL_CASE` which translate naming from camel case to lower camel case. in this case, typically field names remain the same and class names receive a lowercase first character.\n3. `ServerNamingStrategy.UNPROCESSED` which doesn't translate names at all.\n\nTo use *any* other naming, implement you own `PropertyNamingStrategy`, or preferably, the simpler `PropertyNamingStrategyBase` version.\n\nTo set custom name for a specific request or response field, annotate it using:\n```java\n@JsonProperty(\"custom_external_name\")\n```\n\nTo set custom naming strategy to a request or a response class, annotate it using:\n```java\n@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)\n```\n\nEndpoint may be further customized using the following method overrides:\n- `defaultPathProvider` - To be overridden in case path strategy should be changed. This let's you ignore the server naming strategy *and* the class name, and simply return the endpoint path.\n- `defaultInvocationWrapper` - To be overridden in case some operations should be performed before and/or after handling the request. For example, measuring execution time, extra logging, etc...\n- `defaultParamsRequestConditionProvider`, `defaultHeadersRequestConditionProvider`, `defaultConsumesRequestConditionProvider`, `defaultProducesRequestConditionProvider` which may be further explained [in Spring documentation](http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/RequestMappingInfo.html \"in Spring documentation\").","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favivcarmis%2Ftrafficante","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favivcarmis%2Ftrafficante","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favivcarmis%2Ftrafficante/lists"}