This article lists some of the best practices and patterns we use at GOSS when writing end points.
Name and Description
As well as a meaningful name, a short description will help other people (or future you) understand what the end point does.
Help Text
Expand on your description with a few lines of help text. The help text you add appears in the end point request builder in the form designer and other end points.
Schema
Always define parameter and return schema. Not only is this best practice for security, but the schema can be used to generate examples in the end point testing tab and in the request builder.
Tests
Tests allow other developers to see how the end point is used and what a typical response looks like. They can be used to make sure the end point works without the need for additional forms or processes to call it. Tests also appear in the request builder as examples.
Publishing and Deploying
Use the end point namespace to keep related end points organised. Deploy end points to the ajax library with extreme caution - the ajax worker is completely public so end points deployed to it can be called externally and anonymously. The server library can only be called server-side (ie from other end points or from server-side functions in forms).
Script Style
- Don't use
var to define variables, uselet to prevent variable pollution. Useconst rather thanlet if the variable is not reassigned - Have a clearly defined and easily locatable "main" block of code
- Don't access global variables directly in methods, pass them in as parameters
- Use descriptive variable and method names
- Add comments that explain if/why you are doing something unusual. Well written code should tell you what it is doing without the need for comments
- Add JSDoc (opens new window) comments to your methods
- Add defensive checks to all responses from calls to external methods. Log an error/warning if something you are expecting is not returned
- Avoid
do...while loops without good reason - Make use of the end point this helper methods, inparticular
this.callWorkerMethod() to call other workers andthis.invokeEP() to call other end points on the same worker - Use async functions and leverage
await -for...of/in supportsawait ,forEach ,map etc do not - Use
Promise.all() to make multiple non dependant calls to other endpoints/worker methods, ie where each call is not dependant on one of the other calls you are making
Example
This script demonstrates many of the best practices described above.
/**
* Response object format.
* @typedef {Object} ResponseObject
* @property {boolean} success - Indicates if the operation was successful.
* @property {string[]} errorMessages - Array of error messages.
* @property {Object} data - Additional data related to the response.
* @property {boolean} data.wasNameLogged - Indicates if the provided name was logged.
* @property {boolean} data.areTheyOld - Indicates if the age is considered old.
* @property {Object} data.weatherAPIResponse - Weather data retrieved from the API.
*/
/**
* Main entry function that processes the provided name and age.
* @async
* @param {Object} params - The input parameters.
* @param {string} params.name - Provided name.
* @param {number} params.age - Provided age.
* @param {Object} credentials - The user's credentials.
* @returns {Promise<ResponseObject>} A promise that resolves to the response object.
*/
async function example(params, credentials) {
/**
* Main entry function
* @async
* @param {string} name - Provided name.
* @param {number} age - Provided age.
* @returns {Promise<ResponseObject>} A promise that resolves to the response object.
*/
const main = async (name, age) => {
const response = buildResponse();
await performActions(name, age, response);
return response;
};
/**
* Create response object
* @returns {ResponseObject} The response object.
*/
const buildResponse = () => {
return {
"success": true,
"errorMessages": [],
"data": {
"wasNameLogged": false,
"areTheyOld": false,
"weatherAPIResponse": {}
}
};
};
/**
* Perform a number of asynchronous actions using the provided params
* @async
* @param {string} name - The name to be processed.
* @param {number} age - The age to be checked.
* @param {ResponseObject} response - The response object to be modified during processing.
* @returns {Promise<void>} A promise that resolves when all actions are completed.
*/
const performActions = async (name, age, response) => {
const actions = [
writeNameToLog(name, response),
calculateIfAgeIsConsideredOld(age, response),
checkOnTheWeatherInPlymouth(response)
];
await Promise.all(actions);
if (response.errorMessages.length > 0) {
response.success = false;
}
};
/**
* Write the provided name into our logging service
* @async
* @param {string} name - The name to be written to the log.
* @param {ResponseObject} response - The response object to be modified during processing.
* @returns {Promise<void>} A promise that resolves when the name is logged.
*/
const writeNameToLog = async (name, response) => {
await this.logDebug(`The name provided was: ${name}.`);
response.data.wasNameLogged = true;
};
/**
* Determine if the person is old
* @async
* @param {number} age - The age to be checked.
* @param {ResponseObject} response - The response object to be modified during processing.
* @returns {Promise<void>} A promise that resolves when the age is processed.
*/
const calculateIfAgeIsConsideredOld = async (age, response) => {
response.data.areTheyOld = (age > 150);
};
/**
* Retrieve weather data for Plymouth
* @async
* @param {ResponseObject} response - The response object to store weather data.
* @returns {Promise<void>} A promise that resolves when the weather data is retrieved.
*/
const checkOnTheWeatherInPlymouth = async (response) => {
const axios = require("axios");
const URL = "https://api.open-meteo.com/v1/forecast?latitude=50.3715&longitude=-4.143&hourly=temperature_2m,rain&daily=weathercode,temperature_2m_max,temperature_2m_min,sunrise,sunset,precipitation_sum,rain_sum¤t_weather=true&timezone=Europe%2FLondon";
try {
response.data.weatherAPIResponse = ((await axios.get(URL, {
"timeout": 5000
})) || {}).data;
} catch (ex) {
const message = `Error attempting to retrieve weather data - ${ex.name}: ${ex.message}`;
await this.logError(message);
response.errorMessages.push(message);
}
};
return await main(params.name, params.age);
}