Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST API Convention proposal #3237

Open
frankie567 opened this issue Apr 30, 2024 · 0 comments
Open

REST API Convention proposal #3237

frankie567 opened this issue Apr 30, 2024 · 0 comments

Comments

@frankie567
Copy link
Member

This is a proposal of the convention we should adopt for our REST API. The goal is to come up with common pattern across all our resources so it's predictable, consistent and easy-to-use for our users.

Basic endpoints

For this example, we'll consider objects called resource. Every resource has at least:

  • An ID id, an UUID4
  • A creation date, created_at
  • A last update date, modified_at

List resources GET /resources/

This endpoint should list all the resources *accessible to the authenticated subject.

Status codes

  • 200 — The request succeeded.
  • 401 — The request is not authenticated (missing or invalid cookie/token).
  • 422 — The request parameters are invalid (missing property, invalid type, etc.). This is the default in FastAPI when Pydantic validation fails, but we should adopt it too for custom validation (instead of a plain 400).

Output

{
  "items": [
    {"id": "123", "created_at": "2024-06-02T13:37:00Z", "modified_at": null},
    {"id": "456", "created_at": "2024-06-02T13:37:00Z", "modified_at": null}
  ],
  "pagination": {
    "total_count": 2,
    "max_page": 1
  }
}

Pagination

The response is paginated. With no parameter, the first 10 items are shown. This can be controlled using the page and limit query parameters. Maximum limit is 100.

The Resource objects are located in a items property. The pagination property contains information about the total count and maximum page for the current request:

Parameters

Depending on the resource, query parameters can be supported to filter the resulting items. Example:

/resources/?type=foo

Get a single resource GET /resources/{id}

This endpoint retrieve a single resource by their ID.

Output

{
  "id": "123",
  "created_at": "2024-06-02T13:37:00Z",
  "modified_at": null
}

Status codes

  • 200 — The request succeeded.
  • 401 — The request is not authenticated (missing or invalid cookie/token).
  • 404 — The resource does not exist, or the authenticated user can't see it.

Create a resource POST /resources/

Create a new resource with the given JSON payload.

Output

{
  "id": "123",
  "created_at": "2024-06-02T13:37:00Z",
  "modified_at": null
}

Status codes

  • 201 — The request succeeded, a resource was created.
  • 401 — The request is not authenticated (missing or invalid cookie/token).
  • 422 — The request payload is invalid (missing property, invalid type, etc.). This is the default in FastAPI when Pydantic validation fails, but we should adopt it too for custom validation (instead of a plain 400).

Update a resource PATCH /resources/{id}

Update an existing resource by their ID with the given JSON payload. Partial updates are supported meaning that the payload can only contain the properties to change.

Output

{
  "id": "123",
  "created_at": "2024-06-02T13:37:00Z",
  "modified_at": null
}

Status codes

  • 200 — The request succeeded, the resource was updated.
  • 401 — The request is not authenticated (missing or invalid cookie/token).
  • 403 — The request is authenticated, but the authenticated subject can't perform the requested operation.
  • 404 — The resource does not exist, or the authenticated user can't see it.
  • 422 — The request payload is invalid (missing property, invalid type, etc.). This is the default in FastAPI when Pydantic validation fails, but we should adopt it too for custom validation (instead of a plain 400).

Delete a resource DELETE /resources/{id}

Delete an existing resource by their ID. They won't be accessible anymore through List, Get or Update endpoints.

Output

Nothing. Since we delete a resource, it makes sense to have nothing to return.

Status codes

  • 204 — The request succeeded, the resource was deleted.
  • 401 — The request is not authenticated (missing or invalid cookie/token).
  • 404 — The resource does not exist, or the authenticated user can't see it.

Tip

What if we want to archive a resource?

In some circumstances, we may need to archive a resource instead of deleting it. For example, subscription tiers are archivable but not deletable (because customers may still be subscribed to it). The main difference is that an archived resource may be retrieved through List or Get endpoints.

For this, we recommend to simply use the Update and set a specific flag like active, is_archived, etc.

Trigger a background operation POST /resources/{id}/OPERATION_NAME

Sometimes, we might need endpoints to trigger a background operation, like a webhook redelivery or a newsletter sending, that will happen in the worker.

A payload might be passed if needed.

Status codes

  • 202 — The request succeeded, the operation was scheduled but we don't know when it'll be processed and the outcome of it.
  • 401 — The request is not authenticated (missing or invalid cookie/token).
  • 403 — The request is authenticated, but the authenticated subject can't perform the requested operation.
  • 404 — The resource does not exist, or the authenticated user can't see it.
  • 422 — The request payload is invalid (missing property, invalid type, etc.). This is the default in FastAPI when Pydantic validation fails, but we should adopt it too for custom validation (instead of a plain 400).

Specific endpoints

Of course, we'll sometimes need specific endpoints that doesn't fit with this basic picture. A few recommendations though:

  • Choose a sensible verb. Examples:
    • If it changes something on an existing resource, it's probably a PATCH.
    • If it's a reading operation, it's probably a GET.
  • Choose a sensible status code. Examples:
    • If the request creates something, prefer 201 over 200.
    • If it returns nothing, prefer 204.
    • If something fails, choose a 4XX status code. We're not doing GraphQL here 🙃
    • And preferably, something more specific than 400, like 422 or 403.

Error output

The error output is common for any error1. It's a JSON with two properties:

  • type, the raw name of the error class.
  • detail: a user-friendly error message.
{
  "type": "NotPermitted",
  "detail": "You can't do that 😱"
}

Footnotes

  1. It's automatic if you raise an error subclassing PolarError

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Progress
Development

No branches or pull requests

2 participants