Skip to main content
Skip table of contents

API Endpoints Design

Introduction

In this tutorial, we'll guide you through the creation of a simple mockup reference API using the built-in tools in the qibb flow editor. This API is designed to allow you to:

  1. Retrieve a list of all customers

  2. Access information about an individual customer through their unique ID

  3. Create a new customer entry

  4. Update existing customer data

  5. Remove an existing customer record from the system

Tutorial Flow Source Code

To follow this tutorial, you can import the attached flow below. Simply download the attached JSON file below and import it into your flow app.

api_design_flow.json

You can do this by either pressing Ctrl + I simultaneously or by navigating to the main settings and selecting the Import option.

Tutorial

With qibb, you can effortlessly create polished Open API reference files and share them with your partners or customers. Simply start by adding an HTTP In node, connect it to a few change/function nodes, followed by an HTTP Response node, and then fill out the relevant documentation to add the corresponding description of the endpoints and the possible parameters/body.

This example API contains the following calls:

  • GET /customers - to retrieve information for all the customers saved in the CUSTOMERS global variable.

  • GET /customers/:id - to fetch information about a specific customer based on its unique ID.

  • POST /customers - to create a new customer’s object.

  • PATCH /customers/:id - to update an existing customer’s data based on its ID.

  • DELETE /customers/:id - to delete an existing customer from the global context.

API Documentation

API documentation plays a crucial role in giving your API endpoints a professional and well-documented appearance. You can generate the documentation directly within the corresponding HTTP In node by editing the "Docs" section. Within this section, you'll find several tabs:

  • Info: Offering general information about the endpoint.

  • Parameters: Where you can specify the query, body, or path parameters supported by your call.

  • Responses: For configuring the supported HTTP status codes and the format body of the output from your endpoints.

2024-02-05_17-20.png

Adding Parameters Documentation

Info Tab

Here you can specify:

  • Summary: A concise description, displayed alongside your API call, even when the endpoint is collapsed.

  • Description: Offers a more detailed explanation of your call, accessible only when you expand the endpoint.

  • Tags: Defines the tag for this API call, helping organize calls into categories.

  • Consumes: Comma-separated list of input Mime Types supported by your endpoint.

  • Produces: Specifies the format of your responses, e.g. application/json.

  • Security: Allows you to set the required authentication mechanism for this endpoint.

  • Deprecated: a checkbox that marks this particular call as deprecated.

2024-02-05_17-21.png

Info Tab

Parameters

You can add here the supported parameters, by pressing the +parameter button and define:

  • Name: Name of the parameter

  • Parameter’s type: possible types:

    • query

    • header

    • formData

    • body

  • Description: Description of the parameter

  • Type: Choose between string, number, integer, boolean, and array

  • Format: In OpenAPI, the format field typically provides additional information about the data type specified in the type field. For example:

    • formats for "type": "string" can be:

      • date

      • date-time

      • email

      • uuid

      • etc.

    • formats for "type": "number" can be:

      • float

      • double

      • int32

      • int64

      • etc.

  • Required: a checkbox that defines whether the parameter is required or not. The default value is not required.

Path Parameters

By default, the qibb flow editor automatically includes every path parameter defined in the URL within this tab, and marks them as mandatory.

For instance, in the case of a GET request to /customers/:id, the id parameter would be automatically included in this section. However, you still need to define the Description, Type, and Format.

2024-02-05_17-23.png

Definition of Payload Body

Responses

The third tab, called Responses allows you to specify the potential HTTP status codes and the response format for your API call.

Here, you can describe the HTTP status code and define the properties returned from your call.

A comprehensive list of all HTTP Status codes can be found at https://developer.mozilla.org/en-US/docs/Web/HTTP/Status

2024-02-05_17-26.png

Definition of HTTP Responses

API Reference

You can access the API reference documentation that you have created from the API Reference tab as shown in the screenshot below:

2024-02-05_17-44.png

API Reference Tab

From here you can also access the automatically generated URL of your OpenAPI reference in a JSON format, which you can use to load in a swagger editor or provide to a third-party partner.

Customers' Data Storage in the Global Context

Customers Storage

When creating API endpoints, it's common to store the associated data securely for easy access later on. Typically, this involves using a database. However, in this tutorial, we'll keep things simple by using the global context instead.

Initializing the Data

I'm configuring the CUSTOMERS and USER_CREDENTIALS global variables within the change node labeled "Initialize Customers Global Variable". Remember to activate the inject node before proceeding any further.

The API calls write and read the customers' data directly from the global variable called CUSTOMERS, which is saved in the global context of your flow application. When you initialize the context, it will create that variable with an empty array.

Global Context and Customers' Initial Data

API Authentication

API Authentication

It's important to note that the POST, PATCH, and DELETE calls require basic authentication, ensuring secure interactions, while the GET calls are accessible without any sort of authentication.

For authentication, we've chosen basic authentication, requiring both the username and password. The "Initialize Customers Global Variable" action sets the USER_CREDENTIALS global variable to a JSON object containing the username in plain text and a salted hash of the actual password.

In the flow, I've decided to employ bcryptjs, leveraging the blowfish cipher to produce a salted hash of the original password, as demonstrated by the following line of code:

JS
const hashedPassword = await bcrypt.hash(decodedPassword, saltRounds);

The bcrypt hash algorithm is a cryptographic hashing function specifically designed for securely hashing passwords. It is based on the Blowfish cipher and incorporates a unique salt for each password hash. Salting prevents attackers from using precomputed tables (like rainbow tables) to crack passwords, even if multiple users have the same password.

I am using the bcrypt compare function to compare both the plain text password, provided in the API call, and the salted password, stored in the USER_CREDENTIALS global variable:

JS
const passwordMatch = await bcrypt.compare(decodedPassword, saltedPass);

The constant variable passwordMatch would return True when both the salted hash saved in the USER_CREDENTIALS matches the plain text password provided with the API call and False otherwise.

You can read more about the hashing and salting implementation of bcrypt here: https://auth0.com/blog/hashing-in-action-understanding-bcrypt/.

API Endpoints

API Reference Documentation

Please note that all HTTP In nodes are having thoroughly documented Info, Parameters, and Responses that is visible from the API Reference Tab.

Get a List of All Customers

The first API endpoint is fetching information about all customers (GET /customers). Here we simply have the HTTP In node connected to a change node, that sets the msg.payload to the content of the CUSTOMERS global variable and finishes with an HTTP response node.

Get a Specific Customer by Their ID

This call uses a Function node to filter customers and provide data only for the one with a matching ID provided in the URL path parameter :id.

Message Routing

Notice here that we have two outputs of the Function node, each connected to a separate HTTP Response node. This is not required, but it is considered a good practice, as this will give your flow to further extend its functionality and connect it with some extra nodes, in terms of an error.

We are also using the setErrorResponse() function to set the description of the error and the status code in our response.

Create a Customer

When we're creating new objects, we typically use POST calls.

Since this call involves handling customer data, we need authentication to ensure that only authorized users can create customers. This authentication process occurs in the "Check Basic Authentication" function node, using the bcryptjs external npm module.

By default, the username and password are provided in the Authorization header, encoded in base64, and separated by a colon. To proceed, we first fetch the value of this header and decode it using base64, extracting both the username and password like this:

JS
// Extract the base64-encoded credentials part (after "Basic ")
const base64Credentials = credentials.split(' ')[1];

// Decode the base64-encoded credentials
const decodedCredentials = Buffer.from(base64Credentials, 'base64').toString('binary');

// Extract username and password from decoded credentials
const [decodedUsername, decodedPassword] = decodedCredentials.split(':');

Next, we split the decodedCredentials string by the colon sign and compare the plain text password provided in the header with the salted password hash saved in the USER_CREDENTIALS global variable using:

JS
const passwordMatch = await bcrypt.compare(decodedPassword, saltedPass);

If the provided username and password match, we proceed to the next node, which validates the provided customer data.

If the Authorization header is not set or if the username and password don’t match we return a 401 (Unauthorized) status code, if there is a problem with the comparison function we return 500 (Internal Server Error) and in both cases, the msg object is routed through the second output and connected to an HTTP Response node.

In the "Customer's Data Validation" function node, we enforce the minimum required properties for creating a customer object. These properties are the bare minimum required to create a new customer. To facilitate this validation, we utilize a map called validTypeMap, which outlines the valid properties and their respective types. If the incoming request body lacks any mandatory properties or contains unsupported properties or types, we return HTTP status code 400 and a JSON containing a description of the error while redirecting the message object to the second output.

The final step, executed within the "Create Customer" function node, involves the generation of a Universally Unique Identifier (UUID) using the uuid npm module. This UUID, conforming to version 4 standards, serves as a unique identifier for the customer object, ensuring each customer has a unique ID. Upon generation, this UUID is appended to the customer object which is then pushed to the CUSTOMERS global variable.

Example of customers’ data with two objects

Update Existing Customer

The PATCH /customers/:id endpoint allows the modification of a specific customer identified by its unique ID (UUID). This call changes the properties provided in the request body while leaving untouched those not explicitly included.

Again we have a function node, that checks if the username and password passed along with the call are valid, and validates the data from the call, similar to the POST /customers call.

Data Validation

Unlike the customer creation process, no mandatory properties are defined here because the customer object already exists.

Similarly to previous calls, any discrepancies or errors in the modification data trigger an HTTP status code 400 (Bad Request) along with a descriptive error message in the response payload.

Within the "Customer's Data and ID Validation" function node, we verify the existence of the specified customer ID. If the ID is not found, a 404 (Not Found) status code is returned alongside an appropriate error description.

The final function node, "Update Existing Customer" executes the actual update process. Here, the new properties extracted from the request body are assigned to the existing customer (referred to as matchingCustomer), effectively applying the requested modifications using:

JS
// Re-assigns the values from the payload to the matchingCustomer object
Object.assign(matchingCustomer, customer);

// Overwriting the data for the matching customer
customers[customers.findIndex(obj => obj.id === customerId)] = matchingCustomer;

// Pushing the updated customers list to the global variable CUSTOMERS
global.set("CUSTOMERS", customers);

and we push the updated customers array back to the global CUSTOMERS variable and return the modified customer object to the caller.

Delete an Existing Customer

The final call in our mockup API involves deleting a customer through the DELETE /customers/:id endpoint. Similar to previous operations, this call employs the same function node to validate the provided credentials.

This time we only check if the provided customer ID in the URL path of the call exists as the DELETE call doesn’t have any payload.

Upon confirmation of the customer's existence, the corresponding entry is removed from the CUSTOMERS global variable. This removal is accomplished by utilizing the customer's index within the array. The array is then updated by splicing out the element at that index and reassigning the modified array to the global context.

JS
 // Fetching all customers data
const customers = global.get("CUSTOMERS");

// Saving the path parameter ID from the GET call
const customerId = msg.req.params.id;

// Attempting to find a customer with the specified ID
const customerIndex = customers.findIndex(customer => customer.id === customerId);

// Delete the object with the matching ID
const updatedCustomers = customers.splice(customerIndex, 1);

// Update the global 'customers' variable
global.set('customers', updatedCustomers);

After completing the deletion process, we set the HTTP status code to 204 (No Content). This status code indicates to the caller that the requested action has been successfully executed, and no content is being returned in the response body. It serves as a confirmation that the customer deletion operation has been completed without any issues.

Testing the API

In the second logical group, I've developed several flows to accomplish the following tasks:

  1. Retrieve information for all customers.

  2. Retrieve information for a specific customer based on its unique ID.

  3. Execute a flow that involves creating a customer object, modifying it, and then deleting the customer.

Throughout the testing process, I exclusively utilized the qibb OpenAPI node.

To access this node, you must install it from the qibb Node Catalog.

Instructions for installation can be found 👉 https://docs.qibb.com/platform/latest/using-the-qibb-node-catalog-plugin. For further details about the OpenAPI client node, refer to https://docs.qibb.com/platform/latest/openapi-client. To integrate your API reference, follow the steps outlined in https://docs.qibb.com/platform/latest/how-to-expose-your-flow-as-an-openapi.

Once the OpenAPI node is installed, proceed by adding the connection to your OpenAPI reference JSON file, and ensure to configure the Hostname accordingly as shown on the screenshot below:

2024-02-07_16-47.png

Adding OpenAPI JSON Reference & Hostname

Once you do this, you will have all available API endpoints available there under Operations.

2024-02-07_16-54.png

Available Operations

Basic Authentication

The GET /customers and GET /customers/:id endpoints do not require any credentials for access. However, the POST, PATCH, and DELETE calls do require authentication. To execute these operations, you must provide credentials either through the Advanced section of the nodes or by passing them directly before the corresponding OpenAPI node:

JSON
msg.parameters.credentials.BasicAuth = {
  "username": "admin",
  "password": "J8Px(n23E%Sa<.l6)J2Ms6"
}
2024-02-07_16-55.png

Setting Credentials from within the OpenAPI node

Test Flows

Retrieve a List of All Customers

This flow is straightforward, comprising just three components: an inject node, an OpenAPI node configured with the GET /customers operation, and a debug node.

Fetch Information About a Specific Customer

This flow closely resembles the previous one, albeit with the additional requirement of providing the customerId to the OpenAPI node. To achieve this, I've opted to transmit it via msg.customerId, a parameter set within the inject node.

2024-02-07_17-12.png

Setting Customer ID

Create, Update & Delete Customers

To automate the creation, modification, and deletion of customers, I've employed three distinct OpenAPI nodes, each configured with its corresponding operation (POST, PATCH, and DELETE).

Template Nodes

For facilitating customer creation, I've integrated two inject nodes, each accompanied by a template node defining a separate customer object and assigning it to msg.parameters.body outside the OpenAPI node. Alternatively, you can achieve the same by defining the customer object directly in the body field within the OpenAPI node.

The "Save Customer ID and Call Preparation" change node is responsible for storing the generated customer's ID while simultaneously removing the payload and headers.

Subsequently, the next OpenAPI node is configured for PATCH /customers/:id, with the modification properties set directly in the node's body. This node selectively modifies the specified properties of the object while leaving the remainder untouched.

Following this, a change node is utilized to delete several message properties, before the flow proceeds to the final OpenAPI node. Here, we execute the deletion of the previously created customer.

Upon successful execution, we expect to receive a status code of 204 from the last debug node, signaling the successful completion of all operations.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.