Skip to content

Instantly share code, notes, and snippets.

@jahe
Last active April 12, 2022 06:30
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save jahe/10b529c84b6e30d8abf3062dd124e331 to your computer and use it in GitHub Desktop.
Save jahe/10b529c84b6e30d8abf3062dd124e331 to your computer and use it in GitHub Desktop.
REST API Cheatsheet

REST API Cheatsheet

Source: https://www.youtube.com/watch?v=5WXYw4J4QOU

  • POST, GET, PUT, DELETE != CRUD
  • no verbs -> just nouns for Resources: /applications or /applications/123
  • Possible Responses: Collection of Resources (e.g. with Links) or One instance of a Resource
  • Keep your API course grained to be scaleble to future requirements
  • For Resources: Be as specific as possible: /customers vs. /newsletter-customers and /registered-customers
  • PUT for Create: Can be used when the Client has the ability to create an identifier for the Resource himself. But it has to contain a full replacement of the dataset: PUT /applications/123
  • PUT for Update: Has to contain the a full replacement of the dataset (Full Update Operation): The reason why is that PUT has to be Idempotent (??? Does this make sense ???)
  • POST for Create: The request goes to a parent resource like: POST /applications The response has to be 201 Created and have a header with Location of the just created resource: https://api.myapp.com/applications/123
  • POST for Update: Only change one property of the resource (Partial Update): Reponse should be 200 OK. POST is not idempotent. You can't call it multiple times and expect the resource to be identical after eaach request
  • Media-Types: For format specification and parsing rules of the response body for the client. For Request: Accept-Header (Client tells the server what format it wants). For Response: Content-Type-Header (Contains the MIME-Type of the body being sent: e.g. application/json or custom MIME-Types like application/foo+json (the body is structured to the foo media type specification) or application/foo+json;application (add addons/fragments to express not only the format but also the resource inside of it: This is a foo JSON document that happens to be an application Ressource)
  • Base-URL: https://api.foo.com (simpler -> better as customers (developers) want the easiest path to adoption) vs. https://www.foo.com/dev/service/api/rest
  • Versioning: URL https://api.foo.com/v1 (simpler -> pragmatic approach) vs. Media-Type application/json+foo;application,v=1 (URL doesn't change but it's hard for client developers to implement / understand it)
  • Resource Formats:
    • Use camelCase notation for properties
    • Date/Time/Timestamp: There is a standard: Use it: ISO 8601: { "createdTimestamp": "2017-11-15Tt18:10:24.343Z" } and use UTC!
    • HREF: Every Resource contains its own unique link in the response body: { "href": "https://api.foo.com/v1/accounts/123" }
    • Response Body: GET should return a response body always (Captain Obvious). POST should contain it when its feasable to have the most recent version of that object which might be slightly different than the request object: Return it by default and the client always gets the newest version of that object (or as an alternative: give the power to the client with: ?\_body=false to let him decide whether or not he wants the object back)
  • Content Negotiation: Get the content that you want as a client: Accept-Header: Accept: application/json, text/plain vs. Resource Extension: Like /applications/123.json or applications/123.csv (it conventionally overrides the Accept-Header
  • Resource References (Linking):
    • Instance Reference: GET /accounts/123 { "href": "https://api.foo.com/v1/accounts/123", "name": "Tony", ... }
    • Collection Reference: GET /accounts/123 { "href": "https://api.foo.com/v1/accounts/123", "name": "Tony", ..., "directory": { "href": "https://api.foo.com/v1/directories/345" } }
  • Reference Expansion (aka Entity Expansion or Link Expansion): GET /accounts/123?expand=directory { "href": "https://api.foo.com/v1/accounts/123", "name": "Tony", ..., "directory": { "href": "https://api.foo.com/v1/directories/345", "name": "Avengers", ... } } For Caching: Query-Parameters are taking into account for caching rules, so this is being cached under this specific URL
  • Partial Representations: GET /accounts/123?fields=name,surname,directory(name)
  • Pagination: Collection Resource supports querystring params: Offset, Limit: /applications?offset=50&limit=25
    • Example: GET /accounts
    {
    	"href": "https://api.foo.com/v1/accounts",
      "offset": 0,
      "limit": 25,
      "first": { "href": "https://api.foo.com/v1/accounts?offset=0" },
      "previous": null,
      "next": { "href": "https://api.foo.com/v1/accounts?offset=25" },
      "last": { "href": "..." },
      "items": [
      	{ "href": "https://api.foo.com/v1/accounts/1" },
          { "href": "https://api.foo.com/v1/accounts/2" },
          { "href": "https://api.foo.com/v1/accounts/3" },
          ...
      ]
    }
  • Many to Many: Each n:m Mapping is a Resource: e.g. Group to Account: A Group contains Accounts and an Account contains Groups: The Ressource would be GroupMembership:
    • Example: GET /groupMemberships/678
    {
      "href": "https://api.foo.com/groupMemberships/678",
      "account": { "href": "https://api.foo.com/accounts/123" },
      "group": { "href": "https://api.foo.com/groups/234" },
      ...
    }
  • Another Example with the Ressource "Account": It contains the groups directly as well as the groupMemberships: GET /accounts/134
{
	"href": "https://api.foo.com/account/123",
  "name": "Tony",
  ...,
  "groups": [{ "href": "https://api.foo.com/groups/234" }],
  "groupMemberships": { "href": "https://api.foo.com/groupMemberships?accountId=123" }
}
  • Errors: Contain as much Information as possible (Developers are your customers)
    • Example: POST /directories -> 409 Conflict
    {
    	"status": 409,
      "code": 40924,
      "property": "name",
      "message": "A directory named 'Avengers' already exists.",
      "developerMessage": "A directory named 'Avengers' already exists. If you have a stale local cache, please expire it now.",
      "moreInfo": "https://www.foo.com/docs/api/errors/40924"
    }
  • IDs: Should be globally unique. Avoid sequential numbers (possible security risk). Good candidates: UUIDs or Url64
  • HTTP Method Overrides: If clients doesn't support anything other than GET or POST: POST /accounts/123?_method=DELETE
  • Caching & Concurreny Control: ETag == Version Number for a specific Ressource:
    • Example:
    • Server (initial response): ETag: "2344afa23432"
    • Client (later request): If-None-Match: "2344afa23432"
    • Server (later response): 304 Not Modified
  • Security: Avoid sessions when possible (stateless scales better). Authorize based on Ressource content, and not on URLs (they can change). Use an existing protocol like OAuth2, Basic Auth over SSL
  • Maintenance: Use HTTP Redirects (e.g. for moving Ressources -> migrate URLs or deprecate them). Create abstraction layers in your code to minimize change in the API. Use well defined custom Media-Types (If you can do it: It is most resilient to changes over time).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment