-
Notifications
You must be signed in to change notification settings - Fork 50
Backend API
This document describes the backend API architecture, as well as certain guidelines to keep in mind when creating new endpoints.
- Overview of Backend architecture
- Extending new endpoints
- Writing documentation
The Backend API handles API calls from the frontend.
The backend is structured using the MVC design architecture.
Views are responsible for retrieving and showing the data (such as converting results from our PostgreSQL tables to data structures that the backend can manipulate). Can be found in lib/cadet_web/admin_views
and lib/cadet_web/views
.
Example: the IncentivesView
in lib/cadet_web/views/incentives_view.ex
takes in data (formatted in JSON) and renders them to a format that Elixir can manipulate.
Models are data structures which can hold information that other parts of the application uses. Can be found in lib/cadet
Example: the Sublanguage
model in lib/cadet/settings/sublanguage.ex
specifies the fields that the Source Academy Playground sublanguage can be modelled as.
Controllers accept and manage requests from users, modify a model (if required), and convert it into a view (if required). Can be found in lib/cadet_web/admin_controllers
and lib/cadet_web/controllers
. The API endpoints will typically interact with Controllers, and their functions are documented automatically in Swagger.
Example: the Auth
controller in lib/cadet_web/controllers/auth_controller.ex
manages the various functions to deal with authorization: creating a connection (logging in), refreshing the token, as well as logging out. It interacts with the User
model.
The main bulk of functionality is here. The Models can be found in /cadet
, and Controllers and Views are found in /cadet_web
.
All test files go here.
For purpose of illustration, we will give an example of how the Frontend makes an API call to log in a user, and how it's handled in the Backend.
- A user tries to log in to Source Academy. The frontend file,
src/commons/sagas/RequestsSaga.ts
, makes a HTTP request toPOST /auth/login
. - The backend router,
lib/cadet_web/router.ex
, manages this request. It calls thecreate
method inAuthController
. - Within
AuthController
,lib/cadet_web/controllers/auth_controller.ex
, thecreate
method calls thesign_in
function from theAccounts
model (lib/cadet/accounts/accounts.ex
). - Assuming the
sign_in
function returns an 'ok' response, ourAuthController
will then make a call to theAuth
view (lib/cadet_web/views/auth_view.ex
) which renders the JSON web token. - Finally, this web token is returned as a response to the HTTP request.
New routes are added to the lib/cadet_web/router.ex
file.
We use the /admin
route when we need authorization in the form of staff/admin. Specifically. those authorized are:
- Teaching staff (Professors & Avengers)
- Site administrators
Those unauthorized are:
- Students
- Public access (e.g. users of the Github playground)
For /admin
endpoints, authorized users will get data as specified. Unauthorized users will receive HTTP 403 Forbidden
responses, unauthenticated users will receive HTTP 401 Unauthorized
.
For public-facing endpoints, if users are authenticated, only user-specific data can be returned (i.e. no access to other users’ information).
Controllers must be prepended with 'Admin' if it conforms to admin-related tasks. Example: AdminAssetsController
. Similarly, filenames are separated by underscores, such as admin_assets_controller.ex
If the Controller is a noun, use its plural term (e.g. Notifications
, Achievements
etc.)
When new endpoints are created or new functionality is implemented, please add the appropriate tests for the component.
Tests should cover:
- Authorization checks and that proper responses are sent for unauthorized users
- Expected functionality for dummy requests
- Expected functionality for invalid requests (e.g. missing parameters, invalid parameter type etc.)
- Create migrations which that have some initial data that is used for local development purposes
Controllers are documented using Swagger.
Within each Controller, specify:
-
swagger_path
, which describes the endpoint. An example fromnotifications_controller.ex
:
swagger_path :index do
get("/notifications")
summary("Get the unread notifications belonging to a user")
security([%{JWT: []}])
produces("application/json")
response(200, "OK", Schema.ref(:Notification))
response(401, "Unauthorised")
end
The above code documents the :index
method within the Notifications controller, specifying that it is a GET endpoint. We prefer the summary to be a short description of the endpoint, and not a full sentence, so we leave out the period. We specify response type and possible HTTP responses, along with corresponding schema (see below) of the response.
For path endpoint with variables, use /endpoint/{variableInCamelCase}
instead of /endpoint/:variableInCamelCase
.
For endpoint with multiple words, split using underscores. Examples: PUT /user/game_states
, GET /devices/{secret}/mqtt_endpoint
- Additional
Schema
s which represent the response. An example fromnotifications_controller.ex
:
def swagger_definitions do
%{
Notification:
swagger_schema do
title("Notification")
description("Information about a single notification")
properties do
id(:integer, "the notification id", required: true)
type(:string, "the type of the notification", required: true)
read(:boolean, "the read status of the notification", required: true)
assessmentId(:integer, "the submission id the notification references", required: true)
questionId(:integer, "the question id the notification references")
assessment(Schema.ref(:AssessmentInfo))
end
end
}
end
Properties of each field can have type specification as well as a short description. The schema can also link to other schemas defined elsewhere, like the reference to :AssessmentInfo
here
- Use descriptive test descriptions. e.g. if testing endpoints, specify the HTTP request, route, and authentication status, such as
describe "DELETE /:assessment_id, unauthenticated"
- Use
is_nil
rather than== nil
- Use
assert
when checking for positive conditions (i.e.something == true
) andrefute
when checking for negative conditions (i.e.something == false
)