Toggle menu

Writing End Points

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, use let to prevent variable pollution. Use const rather than let 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 and this.invokeEP() to call other end points on the same worker
  • Use async functions and leverage await - for...of/in supports await, 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&current_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);
}

Last modified on 18 April 2024

Share this page

Facebook icon Twitter icon email icon

Print

print icon