{"id":19028705,"url":"https://github.com/researchgate/restler","last_synced_at":"2025-04-23T15:44:39.620Z","repository":{"id":14518350,"uuid":"76667858","full_name":"researchgate/restler","owner":"researchgate","description":"Restler is a project aiming on providing a unified way to easily build REST-services based on document-oriented databases, like MongoDB.","archived":false,"fork":false,"pushed_at":"2025-03-27T09:19:21.000Z","size":396,"stargazers_count":17,"open_issues_count":5,"forks_count":8,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-18T00:57:48.661Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/researchgate.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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,"zenodo":null}},"created_at":"2016-12-16T16:20:05.000Z","updated_at":"2025-02-06T15:42:53.000Z","dependencies_parsed_at":"2023-01-11T19:45:28.946Z","dependency_job_id":"10786a33-f588-48f6-ad93-d7f750e956cf","html_url":"https://github.com/researchgate/restler","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/researchgate%2Frestler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/researchgate%2Frestler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/researchgate%2Frestler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/researchgate%2Frestler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/researchgate","download_url":"https://codeload.github.com/researchgate/restler/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250463197,"owners_count":21434742,"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":[],"created_at":"2024-11-08T21:12:07.360Z","updated_at":"2025-04-23T15:44:39.584Z","avatar_url":"https://github.com/researchgate.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"Restler  \n======================\n\n[![Build Status](https://travis-ci.org/researchgate/restler.svg?branch=master)](https://travis-ci.org/researchgate/restler)\n\nRestler is a project aiming on providing a unified way to easily build REST-services based on document-oriented databases, like MongoDB. It automatically exposes CRUD operations for domain entities without sacrificing flexibility, should you need some extra functionality or business-logic. \n\nThe project consists of two parts: \n\n* **restler-core** - implementation of the query language for REST resources (that's what you should include into your service)\n* **restler-service** - example Dropwizard-based (http://www.dropwizard.io/) web service that uses restler-core and demonstrates its features. For you own service you can simply take this service as a base.\n\n\n\nDefault CRUD Resource\n--------------\n\n**restler** provides default CRUD operations for out of the box. The big focus is made on the _uniform_ data retrieval (that,is a `GET` method), when there is just 1 retrieval endpoint where you can specify **what** (via URL matrix parameters) and **how** (via query parameters) you want to retrieve data.\n\nFor the sake of example, let's assume that you have an `Account` entity that you store in MongoDB, that looks as follows:\n\n```java\n// Morphia annotations for MongoDB collection name and indexes and \n@Entity(\"accounts\")\n@Indexes({\n         @Index(fields = @Field(\"rating\")),\n         @Index(fields = {@Field(\"stats.scoreBreakdown\"), @Field(value = \"rating\", type = IndexType.DESC)}),\n         @Index(fields = @Field(\"nickname\"))\n     }\n)\npublic class Account {\n\n\t// MongoDB primary key (_id)\n    @Id\n    @JsonSerialize(using = ObjectIdSerializer.class)\n    @JsonDeserialize(using = ObjectIdDeserializer.class)\n    private ObjectId id;\n\n    private List\u003cLong\u003e publicationUids;\n\n    private Boolean deleted;\n\n    private List\u003cPublication\u003e publications;\n\n    @JsonSerialize(using = ObjectIdSerializer.class)\n    @JsonDeserialize(using = ObjectIdDeserializer.class)\n    private ObjectId mentorAccountId;\n\n    private Long rating;\n\n    private AccountState state;\n\n\t// different field name stored in MongoDB\n    @Property(\"creaetd\")\n    private Date createdAt;\n\n\t// different field name stored in MongoDB\n    @Property(\"modified\")\n    private Date modifiedAt;\n\n    private Date longDate;\n\n    private AccountStats stats;\n\n    private List\u003cAccountStats\u003e additionalStats;\n\n    private String nickname;\n\n    ...\n    \n}\n```\n\nRestler will take also care of:\n* Transforming ids and criteria from URL to the right types in the code\n* Validating whether your query is `safe` to execute, e.g. it uses MongoDB indexes so that full table scan is not performed. \n\n### GET ####\n\nURL: `/accounts/$segment?$query_params`\n\n`$segment=$id1;$id2;...;key1=value1;key2=value2;...;`\n\nTypical `$query_params`:\n\n* `limit (Integer)` - maximum number of records to fetch\n* `offset (Integer)` - how many records to skip\n* `order (String)` - order by a certain field\n* `fields (String)` - comma-separated list of fields to return \n* `groupBy (String)` - group by a certain field. Starting from version 3.1.0, groupBy can be forbidden by the dao, in order to prevent too much load on the database. \n* `indexValidation (boolean)` - whether enable index validation in your DAO (`true` by default)\n\nExamples:\n\n* `accounts/5718ba18f53e2e6b86f155d1,5718ba19f53e2e6b86f155d7`\n\t* get two accounts by their ids of type Mongo's `ObjectId`\n* `accounts/-;stats.scoreBreakdown=3?fields=rating,stats.followerCnt`\n\t* get accounts where array `stats.scoreBreakdown` contains `3`. Returning fields `rating` \tand a nested field `stats.followerCnt`\n* `accounts/-;rating__ne=0?groupBy=mentorAccountId\u0026limit=2\u0026order=rating`\n\t* Getting all accounts whose rating is not 0,  grouped by mentorAccountId and returned top 2 accounts with highest rating per each mentor id.\t\n* `accounts/-;rating \u003e=3;nickname:$null?limit=10\u0026offset=5` or `accounts/-;rating__gt=3;nickname:$null?limit=10\u0026offset=5`\n\t* get all accounts whose rating is more than `3` and `nickname` is not present in DB (with limit and offset)\n\nBy default the framework reads index information about the collection and forbids queries that don't use an index. For debugging purposes this validation can be disabled by the `indexValidation=false` query parameter\n\nIMPORTANT: If values in the criteria contain reserved or illegal symbols, like space, '=', ';', etc., the URL must be URL-encoded. For example, the '=' sign is used as a key-value separator. In order have to express \u003c= or \u003e=, you have to duplicate the equals sign and URL encode it, e.g. `rating%20\u003e%3D=20` means rating is more or equal than 20 (space got encoded as well). Alternatively, one can use analogous operations that don't require encoding.\n\n##### Alternative syntax for comparison operations\n\nIn order to avoid URL encoding when testing e.g. from a browser (otherwise, you must always encode URL), there is an alternative syntax, e.g. `rating__gte=20` -- returns documents where rating field is greater than 20. Supported operations and their meanings:\n\n* gt : \u003e\n* gte : \u003e=\n* lt : \u003c\n* lte : \u003c=\n* ne : \u003c\u003e \n\n#### Advanced Operations\n\n##### Group by\n\nAdd a `groupBy=$fieldName` as a query parameter: returned results will be grouped by this field. Provided limit will be applied for each group.\n\n##### Querying for documents that match criteria in the same element in the array \nThis is analogue of Mongo's `$elementMatch` operator. For this provide a `syncMatch=$field1,$field$` query parameter.\n\nAssume that `Account` has an array of stats objects that contain `folllowerCnt` and `publicationCnt`. E.g. one account contains `stats=[(1,1), (2,2)]`. Then query: \n\n`/accounts/;stats.publicationCnt=1;stats.followerCnt=2?`\n\nwill return this object because a `stats` array contains elements where `followerCnt==1` and `publicationCnt==2`, whereas a query: \n\n`/accounts/;stats.publicationCnt=1;stats.followerCnt=2?syncMatch=stats`\n\nwill return 0 elements, because the criteria is checked for each element individually. \n\n\n#### Other details\n\n##### Reserved keywords\n\n* `$null` - represents null value\n* `$any` - mostly used for overwriting default query parameters that exist for resource. E.g. `-;deleted=false` could be a default parameter, but in some cases you want to retrieve everything\n* `$exists` - checks whether value exists.\n\n##### Query info\n\nSince typially resources have a default list of fields, some limit and maybe default criteria, it's important to know which query will be ultimately made. For this just append `info` to the normal get query:\nURL: `/accounts/$segment/info?$query_params`\n\nIt will returned the final query fields, it's URL form, so that it can be pasted into the browser URL bar, and also whether query is safe to use, i.e. it uses indexes.  \n\n##### Counting objects without returning results\n\nJust provide `limit=0` query parameter and read the `totalItems` field from the response.\nNote: this behaviour is different from Morphia's where 0 limit is considered to be a query _without_ a limit. \n\n\n### DELETE ####\n\nURL: `/accounts/$segment?$query_params`\n\nDeletion can be done not only by id but also by criteria. Deletion without specifying ids or criteria is forbidden for security reasons.\n\n### POST ####\n\nURL: `/accounts/`\n\n### PUT ####\n\nURL: `/accounts/$id`\n\n\n# For developers\n\n## Project setup\nYou should include restler functionality by including:\n\n```gradle\ncompile group: 'net.researchgate', name: 'restler', version: '$restler-version'\n```\n\n## Exceptions and their mapping\n\nIn case of restler-specific errors a `RestDslException` will be thrown. It's unchecked exception. This exception has a type attribute:\n\n* `PARAMS_ERROR` - thrown when a REST request contains some errors in its syntax. \n* `QUERY_ERROR` - thrown when ServiceQuery (most often manually constructed) has some errors. \n* `ENTITY_ERROR` - thrown when entity to be persisted/modified is invalid or violates some constrains. \n* `DUPLICATE_KEY` - thrown when entity to be persisted/modified is a duplicate of some sort, e.g. violates unique index in Mongo. \n* `GENERAL_ERROR`- unknown error when something unpredictable went wrong, e.g. implementation error or MongoDB is not reachable. \n\nIn order to map those exceptions correctly (i.e. with semantically correct HTTP response code), you can refer to `ServiceExceptionMapper` from the `restler-service` project. Mappings from an exception type to HTTP response code:\n\n* `PARAMS_ERROR` - BAD REQUEST 400\n* `QUERY_ERROR` - BAD REQUEST 400\n* `ENTITY_ERROR` - BAD REQUEST 400\n* `DUPLICATE_KEY` - CONFLICT 409\n* `GENERAL_ERROR` -  INTERNAL SERVER ERROR 500\n\n## Usage in code\n\nMain classes:\n\n* ServiceQuery - representation of a query to a storage.\n* MongoServiceDao - DAO for MongoDB\n* ServiceModel - a basic model that implements typical CRUD operations\n* BaseServiceResource - a basic resource that only exposes retrieve operation.\n* ServiceResource - a basic resource that exposes all CRUD operations.\n\nJust extend a corresponding class (dao, model or resource) with your types for primary key and entity. If you need just CRUD, it's likely that you won't have to do anything more. \n\nYou can always look at restler-service module, to see how these classes are supposed to be used. \n\n## Query Shapes\n\nSince the GET endpoint is pretty flexible it's becomes more important to understand how it is used and if we have performance problems what access patterns cause them. For this a query shapes functionality exists in restler. By providing an implementation of the `StatsReporter` interface, the rest you will get for free.\n\nOne of the possibilities is to log query shapes to graphite. E.g. under `$servicePath/queries/shapes`. The all grouped by a Mongo collection name (`accounts` in the example below).\n\nThe format of a query shape can be described by the following regex:\n\n`(\"IDS\")?(-\"CRITERIA\"-(fieldName_)*fieldName)?(-\"ORDER\"-fieldName)?(-\"GROUPBY\"-fieldName)?(-\"LIMIT\")`\n  \nwhere `fieldName` is a field name from your entity. \n\n* `IDS` means that a primary key (_id) were provided into the query\n* `CRITERIA` tells that filtering was made on those additional fields\n* `ORDER` – sorting was done on a particular field \n* `GROUPBY` returned results will be grouped together by the field provided\u0026 \n* `LIMIT` - a query contained a limit. Typically when querying by a criteria a limit should be provided.\n\n#### Examples\n\n* `-CRITERIA-accountId_rating-ORDER–createdAt`\n\t* A query was filtering on \"accountId\" and \"rating\" fields\n\t* Sorting was done on \"createdAt\" field descending\n* `IDS-CRITERIA-nickname_state`\n\t* `IDS` means that primary keys were provided \n\t* Additionally those entities were filtered on \"nickname\" and \"state\" fields\n* `-CRITERIA-nickname_state_rating-ORDER--createdAt-GROUPBY-mentorAccountId-LIMIT`\n\t* Filtering on three fields (nickname, state, rating)\n\t* Ordering by \"createdAt\" descending (note the minus) \n\t* Grouping the returned results by the \"mentorAccountId\" field\n\t* Limiting every group result to some amount of entries\n\n\n# How to release\nTo build a release of restler:\n\n```bash\n$ gradle release\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fresearchgate%2Frestler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fresearchgate%2Frestler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fresearchgate%2Frestler/lists"}