REST API Convention proposal
Closed this issue · 0 comments
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 plain400
).
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 plain400
).
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 plain400
).
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 plain400
).
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
.
- If it changes something on an existing resource, it's probably a
- Choose a sensible status code. Examples:
- If the request creates something, prefer
201
over200
. - 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
, like422
or403
.
- If the request creates something, prefer
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
-
It's automatic if you raise an error subclassing
PolarError
↩