Skip to content

Endpoint definition format

Agustín Borrego edited this page Dec 6, 2022 · 5 revisions

Defining your API endpoints

The Silence philosophy is that API endpoints are wrappers around SQL operations, and thus they can be defined in a relatively simple manner. We use JSON objects to define endpoints, with the following mandatory properties:

  • route: The route to access this endpoint. You can define URL parameters by prefixing them with $. Note that the final route is composed by prepending the API_PREFIX defined in your settings.py.
  • method: HTTP method for this endpoint, must be GET, POST, PUT or DELETE.
  • sql: SQL query that will run when this endpoint is accessed. The results will be serialized into JSON and sent back to the client. You can use previously defined parameter (either from the route or from the request body, see below) by prefixing them with $.

Additionally, you can add the following optional properties to an endpoint:

  • description: Short textual description for this endpoint.
  • request_body_params: A list of expected parameters in the request body when using POST or PUT. These can be sent encoded in the usual HTTP form format or in JSON.
  • auth_required: boolean, whether a user must be authenticated to use this endpoint (default: false).
  • allowed_roles: List of user roles that are allowed to use this endpoint (see: Restricting endpoint access). Default: ["*"], which means no restrictions.

Endpoints must be defined in JSON files inside the project's endpoints/ folder. Each folder may contain more than one endpoint.

As an example, the following sections will demonstrate how to create basic CRUD endpoints for a Department table whose columns are (departmentId, name, city).

GET endpoints

We can define an endpoint to retrieve all existing Departments like this:

"getAll": {
  "route": "/departments",
  "method": "GET",
  "sql": "SELECT * FROM departments",
  "description": "Gets all departments"
}

URL parameters can be defined and passed on to the SQL query. For example, we can also define an endpoint to retrieve a certain Department by its departmentId:

"getById": {
  "route": "/departments/$departmentId",
  "method": "GET",
  "sql": "SELECT * FROM departments WHERE departmentId = $departmentId",
  "description": "Gets a department by its primary key"
}

In this case, the URL parameter is defined using the $param syntax so Silence knows that it is a variable part of the URL pattern. It is passed to the SQL query using the same syntax by specifying the same parameter name. You can capture and pass as many parameters as you want, in any order, in this manner. On startup, Silence checks that all parameters in the SQL query can be obtained through the URL pattern.

All parameters in a SQL query, whether received via URL pattern or request body, are passed in a safe manner using SQL placeholders.

A user may thus get a specific Department by its ID requesting, for example, GET /api/departments/3

POST/PUT endpoints

Editing data through POST and PUT requests follows the same guidelines, however, most commonly they will receive aditional parameters through the HTTP request body.

An endpoint to create a new Department is associated with a SQL INSERT operation:

"create":{
  "route": "/departments",
  "method": "POST",
  "sql": "INSERT INTO departments(name, city) VALUES ($name, $city)",
  "auth_required": true,
  "description": "Creates a new department",
  "request_body_params": [
    "name",
    "city"
  ]
}

Note that, in this case, the SQL query expects the parameters $name and $city, but they are not defined in the URL pattern. Instead, they are expected in the HTTP POST body. We declare this by adding them in the request_body_params list.

By doing so, Silence knows that you're expecting to receive them in the body of the received HTTP request and can check your SQL query for unexpected parameters.

Silence can extract parameters from request bodies encoded in application/x-www-form-urlencoded or application/json:

For SQL operations other than SELECT, Silence returns the ID of the last modified row (for a POST request, it represents the ID assigned to the newly created resource):

POST /api/departments
Content-Type: application/json
{"name": "Research & Development", "city": "Seville"}

(200 OK)
{
  "lastId": 4
}

PUT requests are similar and combine both URL and body parameters, since update requests are aimed at an already existing resource:

"update": {
  "route": "/departments/$departmentId",
  "method": "PUT",
  "sql": "UPDATE departments SET name = $name, city = $city WHERE departmentId = $departmentId",
  "auth_required": true,
  "description": "Updates an existing department by its primary key",
  "request_body_params": [
    "name",
    "city"
  ]
}

Silence makes sure that all parameters included in your SQL string can be obtained from either the request URL (declared in the endpoint's route) or the request body (declared in request_body_params).

DELETE endpoints

An example of an endpoint to delete a Department by its departmentId is as follows:

"delete": {
  "route": "/departments/$departmentId",
  "method": "DELETE",
  "sql": "DELETE FROM departments WHERE departmentId = $departmentId",
  "auth_required": true,
  "description": "Seletes an existing department by its primary key"
} 

Defining multiple endpoints in one file

You can combine multiple endpoints in a single JSON file for convenience. For example, if you wanted to define all previously shown endpoints in a file endpoints/departments.json, you could do so like this:

{
    "getAll": {
        ...
    },

    "getById": {
        ...
    },

    "create": {
        ...
    },

    "update": {
        ...
    },

    "delete": {
        ...
    } 
}