{"id":28605946,"url":"https://github.com/alexberriman/rest-api-design","last_synced_at":"2026-02-17T19:02:22.130Z","repository":{"id":64366482,"uuid":"574782949","full_name":"alexberriman/rest-api-design","owner":"alexberriman","description":"An opinionated guide on how to design REST APIs.","archived":false,"fork":false,"pushed_at":"2022-12-12T03:19:01.000Z","size":12,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2023-04-04T20:57:34.779Z","etag":null,"topics":["api","api-architecture","api-design","rest"],"latest_commit_sha":null,"homepage":"","language":null,"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/alexberriman.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":"2022-12-06T04:00:23.000Z","updated_at":"2023-02-13T22:25:01.000Z","dependencies_parsed_at":"2023-01-15T13:45:34.644Z","dependency_job_id":null,"html_url":"https://github.com/alexberriman/rest-api-design","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"purl":"pkg:github/alexberriman/rest-api-design","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexberriman%2Frest-api-design","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexberriman%2Frest-api-design/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexberriman%2Frest-api-design/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexberriman%2Frest-api-design/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexberriman","download_url":"https://codeload.github.com/alexberriman/rest-api-design/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexberriman%2Frest-api-design/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259322227,"owners_count":22840360,"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","api-architecture","api-design","rest"],"created_at":"2025-06-11T19:11:54.203Z","updated_at":"2025-10-04T08:49:03.205Z","avatar_url":"https://github.com/alexberriman.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\" id=\"top\"\u003e\n  REST API Design\n  \u003cbr\u003e\n\u003c/h1\u003e\n\u003ch4 align=\"center\"\u003eAn opinionated guide on how to design REST APIs\u003c/h4\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![twitter](https://img.shields.io/badge/Twitter-1DA1F2?logo=twitter\u0026logoColor=white)](https://twitter.com/bezz) [![github](https://img.shields.io/badge/GitHub-100000?logo=github\u0026logoColor=white)](https://github.com/alexberriman/) [![youtube](https://res.cloudinary.com/practicaldev/image/fetch/s--cumRvkw3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://img.shields.io/badge/YouTube-FF0000%3Flogo%3Dyoutube%26logoColor%3Dwhite)](https://www.youtube.com/@alex.berriman) [![linkedin](https://img.shields.io/badge/LinkedIn-0077B5?logo=linkedin\u0026logoColor=white)](https://www.linkedin.com/in/alex-berriman/)\n\n\u003c/div\u003e\n\n## Table of contents\n\n- [Introduction](#introduction)\n- [Basic conventions](#basic-conventions)\n  - [HTTP verbs](#http-verbs)\n  - [HTTP response codes](#http-response-codes)\n  - [URL structure](#url-structure)\n  - [Resources](#resources)\n  - [Data envelopes](#data-envelopes)\n  - [Response types](#response-types)\n- [Operations](#operations)\n  - [Create](#create)\n  - [Read](#read)\n  - [Update](#update)\n  - [Delete](#delete)\n  - [Upserting](#upserting)\n- [Errors](#errors)\n- [Filtering](#filtering)\n- [Sorting](#sorting)\n- [Pagination](#pagination)\n  - [Cursor vs. offset pagination](#cursor-vs-offset-pagination)\n  - [Cursor based pagination](#cursor-based-pagination)\n  - [Page based pagination](#page-based-pagination)\n- [Expanding relations](#expanding-relations)\n- [Partial responses](#partial-responses)\n- [Caching](#caching)\n  - [ETag](#etag)\n  - [If-Modified-Since](#if-modified-since)\n- [Versioning](#versioning)\n- [Reserved query parameters](#reserved-query-parameters)\n\n## Introduction\n\nOver the years I've both written and interfaced with a lot of APIs. Some have been great, some have not. One of the great things about REST is that it's flexible. While there are certain community conventions and best practices in place, it ultimately allows you to design an API however you want. Unfortunately, this flexibility can also often gives rise to poorly designed, ill-thought out APIs that can be incredibly difficult and frustrating to consume.\n\nThe purpose of this repository is to document what I consider to be a great REST API. That is, an API that I, as a software engineer would love to consume and integrate with.\n\nBefore reading on, I want to throw out a quick disclamer: I obviously don't think there's a single _best_ way to design APIs. My views have evolved over time and I expect them to continue to do so. I don't expect everyone to agree on everything. Feel free to pick and choose different parts of this guide if you find them useful. Better yet, if there's something you disagree with or areas of improvement you see, please feel free raise an issue or a PR with suggested changes.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Basic conventions\n\n### HTTP verbs\n\nStandard HTTP verbs should be used:\n\n| HTTP verb | When to use                                          |\n| --------- | ---------------------------------------------------- |\n| `GET`     | Retrieve a list of resources, or a specific resource |\n| `HEAD`    | Retrieve metadata about an individual resource       |\n| `POST`    | Create a new resource                                |\n| `PUT`     | Replace a resource                                   |\n| `PATCH`   | Partially update a resource                          |\n| `DELETE`  | Delete a resource                                    |\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### HTTP response codes\n\nStandard HTTP response codes should be used. `2xx` responses should indicate a success, `4xx` a client error and `5xx` an unhandled service error:\n\n| HTTP code                   | When to use                                                                                                                      |\n| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |\n| `200 OK`                    | The request was a success and the service has data to return                                                                     |\n| `201 Created`               | A new resource was successfully created                                                                                          |\n| `204 No Content`            | The request was successful but the service has no data to return                                                                 |\n| `400 Bad Request`           | The client request was invalid                                                                                                   |\n| `401 Unauthorized`          | The client request lacks valid authentication credentials                                                                        |\n| `403 Forbidden`             | The request contains valid authentication credentials but the identified user is not authorized to access the requested resource |\n| `404 Not Found`             | The requested resource does not exist                                                                                            |\n| `405 Method Not Allowed`    | The HTTP method is not supported on the requested endpoint                                                                       |\n| `409 Conflict`              | Tried to create a resource that already exists                                                                                   |\n| `412 Precondition Failed`   | Access to the target resource has been denied. Generally due to caching header mismatches                                        |\n| `500 Internal Server Error` | A generic server error that should only be returned when a more descriptive error cannot be given                                |\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### URL structure\n\nYou should **always** use the plural spelling variation when grouping resource endpoints:\n\n- It simplifies interfacing with your API (this is the biggest point)\n- It groups all resource child routes together\n- It is generally easier to configure routing rules in a service based architecture\n\nWith the above in mind, your URLs should be configured as follows:\n\n| URL                       | Description                                  | Example             |\n| ------------------------- | -------------------------------------------- | ------------------- |\n| `GET /{resource}/{id}`    | Retrieve a single resource by ID             | `GET /users/abc`    |\n| `HEAD /{resource}/{id}`   | Retrieve metadata of a single resource by ID | `HEAD /users/abc`   |\n| `GET /{resource}`         | Query and retrieve a list of resources       | `GET /users`        |\n| `POST /{resource}`        | Create a new resource                        | `POST /users`       |\n| `PUT /{resource/{id}`     | Replace a resource by ID                     | `PUT /users/abc`    |\n| `PATCH /{resource/{id}`   | Partially update a resource by ID            | `PATCH /users/abc`  |\n| `DELETE /{resource}/{id}` | Delete a resource by ID                      | `DELETE /users/abc` |\n\nUnless there is a strong case not to, you should strive to use non-enumerable unique IDs (such as a uuid). Auto incrementing IDs should be an exception, not the norm.\n\n#### Relations\n\nWhere it makes sense, use nested URLs to return related data: `/{resource}/{id}/{relation}`.\n\n- When the relation is 1:1, use the singular spelling variation (`GET /users/{id}/profile`)\n- When the relation is 1:n, use the plural (`GET /users/{id}/transactions`)\n\nMake sure to set a limit in respect to how deep you nest URLs. To keep things simple, I tend to limit to a single level of nesting:\n\n- Good ✅: `/users/{id}/transactions`\n- Bad ❌: `/users/{id}/transactions/{transactionId}/products/{productId}`\n\nIn situations where you need to query on related resources, encourage consumers to query the resource directly, e.g.:\n\n```\nGET /transactions?user={userId}\u0026product={productId}\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Resources\n\n#### Naming\n\nResources and their properties should be named using `camelCase` (e.g. `createdAt` instead of `created_at`).\n\n#### Required properties\n\n- A resource should always have a read-only property that specifies when it was created (e.g. `createdAt`)\n- A resource should always have a read-only property that specifies when it was last updated (e.g. `updatedAt`)\n\n#### Date format\n\nUse [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) to represent all dates and times (e.g. `2020-01-01T00:00:00.000Z`) For simplicity, store all datetimes with a zero offset (that is, store in UTC time). If you need to store timezone information, do it in a separate property. A user's timezone may change, but the datetime they completed an action will not.\n\n#### Spelling localization\n\nAlways use American spelling. Users consuming your API shouldn't have to know the intracices between American, Australian, Canadian and British English.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Data envelopes\n\nData envelopes are unnecessary and should not be used. For a single resource, return the object directly. For multiple resources, return an array of objects. If you need to return additional information, use custom HTTP headers.\n\n#### Example with data envelope (bad ❌)\n\n```json\n{\n  \"data\": {\n    \"id\": \"aaa\",\n    \"name\": \"Albert Einstein\"\n  }\n}\n```\n\n#### Example without data envelope (good ✅)\n\n```json\n{\n  \"id\": \"aaa\",\n  \"name\": \"Albert Einstein\"\n}\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Response types\n\nAPIs should generally return a response in a single content type (e.g. json). In order to keep your API as simple as possible, I'd recommend only implementing a single content type unless there was a strong need to support multiple (e.g. json + xml).\n\nIf you do need to support multiple content types, do so through the [Accept](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) request header and return the response in the requested format through the [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) response header.\n\nIf the client requests data in a content type that isn't supported, return `415 Unsupported Media Type`.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Operations\n\nStandard CRUD operations should apply. In a situation in which certain operations are not available (read-only resources might not be available to update/delete for example), the service should return `405 Method Not Allowed`.\n\n### Create\n\n`POST /{resource}`\n\nA `POST` request to the resource should return a `201 Created` response with the ID of the created resource:\n\n```jsonc\n// 201 Created\n{\n  \"id\": \"aaaa-bbbb\"\n}\n```\n\nIf a resource has a unique constraint and already exists (e.g. a user resource might have a unique constraint on the `email` property), it should return a `409 Conflict` response.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Read\n\n#### Retrieving a single resource by ID\n\n`GET /{resource}/{id}`\n\nA `GET` request to the single resource instance with an ID should return a `200 OK` response with the resource:\n\n```jsonc\n// GET /users/aaa\n{\n  \"id\": \"aaa\",\n  \"name\": \"Steve Smith\",\n  \"age\": 30\n}\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n#### Searching for/retrieving a list of resources\n\n`GET /{resource}`\n\nA `GET` request to the resource should return a `200 OK` response with a list of the resources:\n\n```jsonc\n// GET /users\n[\n  {\n    \"id\": \"aaa\",\n    \"name\": \"Steve Smith\",\n    \"age\": 30\n  },\n  {\n    \"id\": \"bbb\",\n    \"name\": \"Alyssa Healy\",\n    \"age\": 32\n  }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n#### Retrieve resource metadata\n\n`HEAD /{resource}/id`\n\nIt's often useful to know information about a resource (e.g. whether it exists, its [ETag](#etag), last modification time, content-type, content-length etc.) without wanting to actually fetch the resource itself.\n\nA `HEAD` request to a resource should return the same response headers as a `GET` request but with an **empty response body**.\n\n### Update\n\nWhen you update a resource, the service should return a partial object with updated values. It's not uncommon for a resource to have various read-only computed properties. Returning updated values allows consumers of your API to keep the local state of their application in sync without having to send a follow-up `GET` request after updating a resource.\n\nUsing the following json:\n\n```jsonc\n[\n  {\n    \"id\": \"aaa\",\n    \"name\": \"Ellyse Perry\",\n    \"highScore\": 200,\n    \"average\": 73.2 // read-only property\n  },\n  {\n    \"id\": \"bbb\",\n    \"name\": \"Meg Lanning\",\n    \"highScore\": 93,\n    \"average\": 31.36\n  }\n]\n```\n\n#### Replacing a resource\n\n`PUT /{resource}/{id}`\n\nA `PUT` request should replace the entire resource and return a `200 OK` response with the updated values:\n\n**Request:**\n\n```jsonc\n// PUT /users/bbb\n{\n  \"name\": \"Meg Lanning\",\n  \"highScore\": 100\n}\n```\n\n**Response:**\n\n```jsonc\n// 200 OK - return properties that have changed\n{\n  \"highScore\": 100,\n  \"average\": 32.32\n}\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n#### Partially updating a resource\n\n`PATCH /{resource}/{id}`\n\nA `PATCH` request with a partial object of the resource should return a `200 OK` response with the updated values:\n\n**Request:**\n\n```jsonc\n// PATCH /users/aaa\n{\n  \"highScore\": 400\n}\n```\n\n**Response:**\n\n```jsonc\n// 200 OK - return properties that have changed\n{\n  \"highScore\": 400,\n  \"average\": 89.12\n}\n```\n\n**Working with complex types**\n\nIt's not uncommon for resources to have non-simple types (e.g. arrays of objects):\n\n```jsonc\n// athlete object:\n{\n  \"id\": \"aaa\",\n  \"name\": \"aaron finch\",\n  \"series\": [\n    {\n      \"opponent\": \"india\",\n      \"scores\": [\n        { \"score\": 100, \"notOut\": false },\n        { \"score\": 32, \"notOut\": false },\n        { \"score\": 242, \"notOut\": true }\n      ]\n    },\n    {\n      \"opponent\": \"england\",\n      \"scores\": [\n        { \"score\": 12, \"notOut\": false },\n        { \"score\": 1, \"notOut\": true }\n      ]\n    }\n  ]\n}\n```\n\nTo partially update arrays and nested properties without having to replace the entire array, use [RFC-6902 JSON Patch](https://www.rfc-editor.org/rfc/rfc6902).\n\n**Example:** update the athlete's latest score and mark them as out:\n\n```jsonc\n// PATCH /athlete/aaa\n[\n  {\n    \"op\": \"replace\",\n    \"path\": \"series/1/scores/1\",\n    \"value\": { \"score\": 42, \"notOut\": false }\n  }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Delete\n\n`DELETE /{resource}/{id}`\n\nA `DELETE` request should delete the resource and return a `204 No Content` response with an empty response body.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Upserting\n\nIt's fairly common to have an APIs `PUT /{resource}/{id}` endpoint offer upserting capabilities. I **do not** like this as it:\n\n- Violates the single responsibility principle - you are creating a single endpoint that allows a consumer to both create **and** update a resource\n- Can _silently_ fail - if a user assumes a resource to exist and wants to update it, a `2xx` response would indicate the operation completed successfully, where in fact a new resource would have been created.\n\nI've observed too many real-world issues in production related to upserting. If an application wants to upsert, it can easily do so by sending a:\n\n1. `HEAD` request to `/{resource}/{id}` to check if the resource exists\n2. `POST` request to `/{resource}` if it does not, or a `PUT` request to `/{resource}/{id}` if it does\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Errors\n\n### Response codes\n\n- `4xx` HTTP codes should be returned for **client** errors\n- `5xx` HTTP codes should be returned for **server** serrors\n\nSee [HTTP response codes](#http-response-codes) for more information on which error code to return.\n\n### Basic conventions\n\n- Errors should always be returned in a consistent format\n- When an error cannot be inferred from the the response body, the inferred meaning from the HTTP code should take precedence (e.g. `404 Not Found` means the requested resource could not be found)\n- All detected errors should be returned in a single response\n\n### Data envelope\n\nSimilar to above, an `errors` data envelope is unnecessary and should not be returned. The fact that the application is responding with a list of errors can be inferred from the `4xx` HTTP response.\n\n### Error object\n\n| Property | Type     | Required | Description                                                |\n| -------- | -------- | -------- | ---------------------------------------------------------- |\n| property | `string` | ❌       | The property name that is the cause of the error           |\n| code     | `string` | ✔️       | `CAPS_CASE` constant error code to be used programatically |\n| message  | `string` | ✔️       | Human-readable description of the error.                   |\n\nThe human readable message can change over time and should not constitute a breaking change. `code` constants should not be changed (or any changes to these should constitute a breaking change).\n\n### Example response\n\n```json\n[\n  {\n    \"property\": \"dateOfBirth\",\n    \"code\": \"INVALID\",\n    \"message\": \"You must be 18 years old to sign up for an account\"\n  },\n  {\n    \"property\": \"age\",\n    \"code\": \"INVALID\",\n    \"message\": \"Expected number, got \\\"string\\\"\"\n  }\n]\n```\n\n`property` may be omitted if the error is not in relation to a single property. For example:\n\n```json\n[\n  {\n    \"code\": \"IS_CLOSED\",\n    \"message\": \"The competition has closed. Please try again next year.\"\n  }\n]\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Filtering\n\nFiltering should be done on property names in the query parameters. For example, a simple query where you only want to return `active` users would be achieved through `GET /users?state=active`.\n\nAdvanced filtering can be achieved with the following operators:\n\n### Basic operators\n\n| Operator     | Description                            | Example                             |\n| ------------ | -------------------------------------- | ----------------------------------- |\n| `gt`         | Greater than                           | `/users?age[gt]=21`                 |\n| `gte`        | Greater than or equal to               | `/users?age[gte]=18`                |\n| `lt`         | Less than                              | `/users?age[lt]=25`                 |\n| `lte`        | Less than or equal to                  | `/users?age[lte]=100`               |\n| `eq`         | Equal to                               | `/users?name[eq]=Albert%20Einstein` |\n| `contains`   | Contains                               | `/users?name[contains]=albert`      |\n| `startsWith` | Starts with                            | `/users?name[startsWith]=a`         |\n| `endsWith`   | Ends with                              | `/users?name[endsWith]=ein`         |\n| `in`         | Comma separated list of allowed values | `/users?hairColor[in]=brown,red`    |\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Null operators\n\n`null` is a special use case, as you may (however unlikely) want to query on the string literal `\"null\"` as opposed to a `null` value. Because of this, the `isNull` operator is available for use\\*:\n\n| Operator | Description     | Example                       |\n| -------- | --------------- | ----------------------------- |\n| `isNull` | Value is `null` | `/users?dateOfDeath[isNull]=` |\n\n_\\* where possible try to avoid the use of `null` as it is often unclear what a `null` value represents._\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Not condition\n\nUse `!=` to indicate a not condition. For example, to query all users whose name does not start with the letter a:\n\n```\nGET /users?name[startsWith]!=a\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Case sensitivity\n\nString operators (`contains`, `startsWith`, `endsWith`, `in`) can be prefixed with `i:` (shorthand for `insensitive:`) to indicate case insensitivity. By default, filtering is assumed to be case sensitive.\n\n**Example:** retrieve all users whose name starts with a\n\n```\nGET /users?name[i:startsWith]=a\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Type coercion\n\nValues sent through query parameters should be coerced where possible into the type of the property being filtered against:\n\n- `0`/`1` should be interpreted as `false`/`true` for `booleans`\n- `true` / `false` should be interpreted as `true` / `false` for `booleans`\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Sorting\n\nSorting should be done through the `sortBy` URL parameter in the format `[propertyName].[sortOrder]`, where `sortOrder` is either `asc` or `desc`. For example, to search for users and sort by their first name in descending order, you would use:\n\n```bash\nGET /users?sortBy=firstName.desc\n```\n\nYou can sort on multiple attributes through a comma:\n\n```bash\nGET /users?sortBy=firstName.desc,lastName.asc\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Pagination\n\nThe [HTTP Link](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) header should be used to return links to the paginated pages.\n\n### Cursor vs. offset pagination\n\nI want to limit myself from saying _\"you should always use cursor based pagination over offset based pagination\"_, as I don't want to wade too much into the implementation side of things and make assumptions about the type of API you're building. Typically, I would always default to implementing cursor-based pagination unless there was a strong case not to, and I would recommend you do as well. Amongst other things, it:\n\n- Eliminates the possibility of fetching the same items (or skipping items), which can occur when a collection is frequently being written to\n- Tends to scale better as your database grows in size\n- Provides a simpler interface both to querying over the API and to the end user navigating your data\n\n### Cursor based pagination\n\n#### Request\n\nThe page cursor should be passed through a `cursor` property in the URL:\n\n```bash\nGET /users?cursor=\u003cpage-cursor\u003e\n```\n\n#### Response\n\nLinks to the `next`, `previous` and `first` page (when returning `first` is applicable) should be returned:\n\n```\nlink: \u003chttps://example.rest/users?cursor=\u003ccursor\u003e; rel=\"previous\", \u003chttps://example.rest/users?cursor=\u003ccursor\u003e; rel=\"next\", link: \u003chttps://example.rest/users?cursor=\u003ccursor\u003e; rel=\"first\"\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Page based pagination\n\n#### Request\n\nThe `page` and `perPage` query parameters should be used to query specific pages. `perPage` should default to an amount relative to your use case (e.g. `25`) and have a maximum value also relative to your case (e.g. `100`).\n\n#### Response\n\nHTTP links to the previous, next, first and last pages should be returned. The total amount of records is not directly returned, but can be loosely inferred by the URL of the `last` page. If you need to return the total amount of records, use a custom HTTP header.\n\nAlthough a link to the first page may seem unnecessary, its returned to assist consumers of your API from having to parse and generate URLs.\n\n```\nlink: \u003chttps://example.rest/users?page=3; rel=\"previous\", \u003chttps://example.rest/users?page=5; rel=\"next\", link: \u003chttps://example.rest/users?page=1; rel=\"first\", link: \u003chttps://example.rest/users?page=22; rel=\"last\"\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Expanding relations\n\nIt's fairly common for resources to have relationships between them. Often a response will contain an ID of a related resource. For example, a `transaction` may have an associated `user`. To prevent consumers from having to send multiple sequential HTTP requests to retrieve this information, you should allow them to expand those objects inline with the `expand` request parameter:\n\n```\nGET /transactions/{id}?expand=user\n```\n\nFurthermore:\n\n- You can expand multiple relations at once by separating them with a comma\n- You can expand nested relations with a period (`.`)\n- Expanded relations should have a maximum depth of three levels.\n\nWith the above in mind:\n\n- Good ✅: `/transactions/{id}?expand=user,user.profile`\n- Bad ❌: `/transactions/{id}?expand=user,user.profile,user.profile.address,,user.profile.address.country`\n\n### Example\n\nA request to `GET /transactions/\u003cid\u003e` might return:\n\n```json\n{\n  \"id\": \"aaaa-bbbb\",\n  \"title\": \"Sample event subscription\",\n  \"user\": \"zzzz-0000\"\n}\n```\n\nWhere `user` references the ID of the `user` who made the transaction. To automatically expand on the user details, you would request `GET /transactions/{id}?expand=user,user.profile`, which might return:\n\n```json\n{\n  \"id\": \"aaaa-bbbb\",\n  \"title\": \"Sample event subscription\",\n  \"user\": {\n    \"id\": \"zzzz-0000\",\n    \"username\": \"albert.einstein\",\n    \"email\": \"albert.einstein@example.com\",\n    \"profile\": {\n      \"gender\": \"male\",\n      \"about\": \"Physicist\"\n    }\n  }\n}\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Partial responses\n\nIt's often the case when consuming an API you're only interested in a small part of the response. In this situation, you should be able to request only certain properties through the `fields` query parameter.\n\nOn the implementation side of things, I tend to use [JSONPath](https://github.com/json-path/JsonPath), though [XPath](https://en.wikipedia.org/wiki/XPath) is a nice alternative when working with XML.\n\n### Basic rules (assuming JSONPath):\n\nTo simplify filtering for the user, I make the JSONPath selector prefixes `$.`/`$..` optional.\n\nGiven the following `athlete` resource:\n\n```json\n{\n  \"name\": \"Ian Thorpe\",\n  \"nationality\": \"AU\",\n  \"born\": 1982,\n  \"olympics\": [\n    {\n      \"year\": 2000,\n      \"location\": \"Sydney, Australia\",\n      \"medals\": [\n        { \"type\": \"GOLD\", \"sport\": \"swimming\", \"event\": \"400 m freestyle\" },\n        { \"type\": \"GOLD\", \"sport\": \"swimming\", \"event\": \"4x100 m freestyle\" },\n        { \"type\": \"GOLD\", \"sport\": \"swimming\", \"event\": \"4x200 m freestyle\" },\n        { \"type\": \"SILVER\", \"sport\": \"swimming\", \"event\": \"200 m freestyle\" },\n        { \"type\": \"SILVER\", \"sport\": \"swimming\", \"event\": \"4x100 m medley\" }\n      ]\n    },\n    {\n      \"year\": 2004,\n      \"location\": \"Athens, Greece\",\n      \"medals\": [\n        { \"type\": \"GOLD\", \"sport\": \"swimming\", \"event\": \"200 m freestyle\" },\n        { \"type\": \"GOLD\", \"sport\": \"swimming\", \"event\": \"400 m freestyle\" },\n        { \"type\": \"SILVER\", \"sport\": \"swimming\", \"event\": \"4x200 m freestyle\" },\n        { \"type\": \"BRONZE\", \"sport\": \"swimming\", \"event\": \"100 m freestyle\" }\n      ]\n    },\n    {\n      \"year\": 2012,\n      \"location\": \"London, England\",\n      \"medals\": []\n    }\n  ]\n}\n```\n\nThe following rules apply:\n\n| Query (`?filter=???`)                       | Result                                                          |\n| ------------------------------------------- | --------------------------------------------------------------- |\n| `name,nationality`                          | The athlete name and nationality                                |\n| `olympics`                                  | The entire olympics object                                      |\n| `name,olympics.year,olympics.location`      | The athlete name and year/location of olympics they competed in |\n| `olympics[0]`                               | The first olympics                                              |\n| `olympics[0:1]`                             | The first two olympics                                          |\n| `olympics[-2]`                              | The second to last olympics                                     |\n| `olympics[1:2]`                             | All olympics from index 1 (inclusive) until index 2 (exclusive) |\n| `olympics[1:]`                              | All olympics from index 1 (inclusive) to last                   |\n| `olympics[?(@.year \u003e 2000)]`                | All olympics after the year 2000                                |\n| `olympics[?(@.event =~ /^\\dx\\d+\\sm\\s.*$/)]` | All olympics matching regex for team events                     |\n\nSee [JsonPath](https://github.com/json-path/JsonPath) for more information.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Caching\n\nBoth [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) and datetime [If-Modified-Since](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since) caching should be supported.\n\n### ETag\n\nWhen a resource is retrieved over the API, an `ETag` header should be returned with a hashed value that uniquely identifies the version of the resource at that point in time.\n\n#### Preventing write collisions\n\nWhen updating resources, the `ETag` and `If-Match` header can be used to ensure only a specific version of a resource is being mutated. For example, when requesting a resource with `GET /article/{id}` the service should return an ETag header in the response:\n\n```\nETag: \"aaaa-zzzz\"\n```\n\nWhen updating the record, an `If-Match` header can be supplied to ensure only that version of the resource is being updated:\n\n```\nPATCH /article/{id}\nIf-Match: \"aaaa-zzzz\"\nContent-Type: application/json\n\n{\n  \"title\": \"My updated title\"\n}\n```\n\nIf the hashes don't match, it means the document has likely been updated, and the server should respond with a `412 Precondition Failed` response.\n\n#### Reading resources\n\nThe `If-None-Match` header can be used to only retrieve the resource if the hash of the most recent version doesn't match the hash supplied:\n\n```\nGET /article/{id}\nIf-None-Match: \"aaaa-zzzz\"\n```\n\nIf the hashes don't match, a `200 OK` response should be returned with the resource. If the hashes do match, a `304 Not Modified` response should be returned with an empty response.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### If-Modified-Since\n\nIt's often also useful to cache resources in respect to time. This is particularly useful given all resources should have a computed `updatedAt` property with the time the resource was last modified.\n\nA requests with an [RFC 1123](http://www.ietf.org/rfc/rfc1123.txt) timestamp in the `If-Modified-Since` request header should return a `304 Not Modified` header and empty request body if the resource was last modified _before_ that time. If the resource was modified after that time, a standard `2xx` response should be returned.\n\n```\nGET /article/{id}\nIf-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n### Priority\n\nWhen both a `If-Modified-Since` and an `If-Match`/`If-None-Match` header are supplied, priority should always be given to the `ETag` headers.\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Versioning\n\nVersioning strategies are very situational, and I don't think there's any one approach that can always be considered as the best option.\n\nFor simplicity, default to versioning in the URL through a `v{number}` prefix. e.g. `/v1/*`, `/v2/*`. On an individual service, I generally prefer versioning before the resource at the service level:\n\n- `/v1/users/{id}`\n- `/v1/users`\n- `/v1/invoices`\n\nIf your resources are fairly independent of each other you can choose to version at the resource level, however I tend to think this can sometimes increase the complexity of an API:\n\n- `/users/v3/{id}`\n- `/users/v3`\n- `/transactions/v2/{id}`\n\nWhen working in a service-oriented architecture, version at the service level:\n\n**User service:**\n\n- `GET /users/v1/{id}` -\u003e routes to `v1` of the `user` service\n- `GET /users/v2` -\u003e routes to `v2` of the `user` service\n- `GET /users/v3/{id}/transactions` -\u003e get `user` transactions from `v3` of the `user` service\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n\n## Reserved query parameters\n\nWith all of the above in mind, the following query parameters are reserved:\n\n- **[\\[sorting\\]](#sorting)** `sortBy`\n- **[\\[pagination\\]](#pagination)** `cursor` (when using [cursor based pagination](#cursor-based-pagination))\n- **[\\[pagination\\]](#pagination)** `page` / `perPage` (when using [page based pagination](#page-based-pagination))\n- **[\\[expanding relations\\]](#expanding-relations)** `expand`\n- **[\\[partial responses\\]](#partial-responses)** `fields`\n\nWhere possible, try to avoid resource property names that conflict with the above reserved keywords. If for whatever reason there is a conflict, the reserved keywords should take precedence. You can query on the conflicting resource keywords by prefixing the property with a `$` symbol:\n\n**Example:** get all users who have an `expand` value of `true` on the paginated cursor `xxxx`:\n\n```\nGET /users?cursor=xxxx\u0026$expand=true\n```\n\n\u003cdiv align=\"right\"\u003e\u003ca href=\"#top\"\u003eBack to top\u003c/a\u003e\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexberriman%2Frest-api-design","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexberriman%2Frest-api-design","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexberriman%2Frest-api-design/lists"}