Write RESTFull APIs to connect Ballerina backends to Frontend Applications

Sasindu Alahakoon
Ballerina Swan Lake Tech Blog
7 min readMar 13, 2024

This article was written using Ballerina Swan Lake Update 8(2201.8.4).

Introduction

In the continually advancing field of software development, the integration of backend and frontend components in applications constitutes a crucial process. This article serves as a guide to the clear and straightforward methodology of connecting Ballerina backends with frontends using REST API technology.

Let’s embark on this journey of connecting the dots between backends and the user-friendly front ends that make your software shine.

What is Ballerina?

Ballerina stands as a unique open-source, cloud-native programming language with a specialized focus on integration. Its extensive library of network protocols, data formats, and connectors empowers developers to effortlessly connect frontend technologies and microservices with very few lines of code.

What is REST?

A REST API is a standardized set of rules and conventions that enables communication and data exchange between different software applications over the internet. It relies on the use of standard HTTP methods like GET, POST, PUT, and DELETE to perform various actions on resources. REST APIs are stateless, which means each request from a client contains all the information needed and resources are typically identified by unique URLs.

Let’s discover the powerful REST capabilities of Ballerina using a real-world application.

Use case scenario

MegaPort Terminal is an essential hub for logistics. They aim to improve their daily operations such as order management by using advanced technology solutions provided by Ballerina. They have already developed their front-end application using React.

Prerequisite

Before diving into programming, let’s install Ballerina first. You can download the latest Ballerina version from here. Then before starting the coding, let’s install the Ballerina VSCode extension from here.

REST? Ballerina is REST

Now, let’s get our hands dirty to see the process of building Ballerina RESTful APIs that smoothly connect with MegaPort’s React front-end.

First of all, let’s create a new Ballerina project for this. For that, execute the bal new command to create a new Ballerina package.

$ bal new megaport```

For more details about creating Ballerina projects, see organizing ballerina code.

The created Ballerina project will have the folder structure below.

megaport
…. devcontainer.json
…. .gitignore
…. main.bal
…. Ballerina.toml

Rename the main.bal file to server.bal, as we are going to implement a simple Ballerina HTTP service to manage Megaport’s orders in that file.

The Ballerina code below initiates the service in the localhost at port 9090, and adds /sales as the base path.

import ballerina/http;

service /sales on new http:Listener(9090) {
}

Then, let’s add four simple resource functions for the following use cases.

1. Create a new order
Resource path - /orders
Resource type - POST
Request payload type - Order

2. View all orders
Resource path - /orders
Resource type - GET

3. View a specific order based on an order ID
Resource path - /orders/<order_id>
Resource type - GET

4. View a list of orders based on the order status that are owned by a specific customer.
Resource path - /customers/<customer_id>/orders?status=<status>
Resource type - POST
Query param - status

Before creating the resource functions, let’s define a Ballerina record type to represent the order type that is used in the payload and response types.

Import ballerina/http;

type Order record {|
readonly string id;
string customerId;
string? shipId;
string date;
OrderStatus status;
int quantity;
string item;
|};

enum OrderStatus {
PENDING,
SHIPPED,
DELIVERED,
CANCELED,
RETURNED
};

service /sales on new http:Listener(9090) {
}

Here, the OrderStatus is an enum that represents the status of an Order. The type of the shipId is string?, which means it can be string or null.
Then, let’s define a simple orders table to store the Megaport’s current order list in memory.

final table<Order> key(id) orders = table [
{id: "HM-278", quantity: 5, item: "TV", customerId: "C-124", shipId: "S-8", date: "22–11–2023", status: PENDING},
{id: "HM-340", quantity: 3, item: "IPhone 14", customerId: "C-73", shipId: "S-32", date: "12–11–2023", status: DELIVERED}
];

You can use an SQL/NoSQL database to store these orders as well. In that case, you can use the Ballerina persist module to create, read, update, and delete orders in the database. However, as our objective is to demonstrate Ballerina REST capabilities, we will store orders in a Ballerina table.

Now, let’s add a resource function for creating a new order.

// Create a new order
resource function post orders(Order orderRequest) returns http:Created|http:BadRequest {
if orders.hasKey(orderRequest.id) {
return <http:BadRequest>{
body: string `Order id already exists. Order ID: ${orderRequest.id}`
};
}
orders.add(orderRequest);
return http:CREATED;
}

Here, the resource type is POST and the resource path is /orders. This resource can be accessed using the following HTTP endpoint.

POST http://localhost:9090/sales/orders

E.g. curl command to add a sample order:
curl -X POST -d '<order details>' http://localhost:9090/sales/orders

This resource accepts a request payload type of Order and this function will automatically convert the incoming JSON payload into a Ballerina record without user intervention.
It returns an HTTP 201 if the order creation is successful. If the order creation fails, it returns an HTTP 400 status code.

Now, let’s add a resource function viewing all orders.

// View all orders
resource function get orders() returns Order[] {
return orders;
};

The type of this resource is GET and the resource path name is /orders.

Here, this resource function returns a list of orders and it can be accessed using the following sample URL.

GET http://localhost:9090/sales/orders

Let’s add the resource functions for viewing a specific order based on the id.

// View a specific Order based on order id
resource function get orders/[string id]() returns Order|http:NotFound {
return orders[id] ?: <http:NotFound>{
body: string `Order not found. Order ID: ${id}`
};
};

Here, the id variable is modeled as a path parameter. If the database contains an order with that ID, it will return that order. Otherwise, it returns an HTTP 404 status code.

The following HTTP endpoint can be used to access the order with the id value HM-123.

GET http://localhost:9090/sales/orders/HM-123

Now, let’s add a resource function for viewing a list of orders based on the order status and the customer id.
Here, you need to use a query parameter for the status. In Ballerina, query parameters for GET resource functions can be modeled as function arguments.

resource function get customers/[string customerId]/orders(OrderStatus status = PENDING) returns Order[] {
return from Order entry in orders
where entry.customerId == customerId && entry.status == status
select entry;
};

Here, the default value for the status query parameter is PENDING. The customerId variable is taken as a path parameter.
The following HTTP endpoint can be used to access this resource.

GET http://localhost:9090/customers/C-123/orders?status=SHIPPED

The complete Ballerina code for Megaport order management REST service is shown below:

import ballerina/http;

type Order record {|
readonly string id;
string customerId;
string? shipId;
string date;
OrderStatus status;
int quantity;
string item;
|};

enum OrderStatus {
PENDING,
SHIPPED,
DELIVERED,
CANCELED,
RETURNED
};

final table<Order> key(id) orders = table [
{id: "HM-278", quantity: 5, item: "TV", customerId: "C-124", shipId: "S-8", date: "22–11–2023", status: PENDING},
{id: "HM-340", quantity: 3, item: "IPhone 14", customerId: "C-73", shipId: "S-32", date: "12–11–2023", status: DELIVERED}
];

service /sales on new http:Listener(9090) {
resource function get orders() returns Order[] {
return orders.toArray();
};

resource function get orders/[string id]() returns Order|http:NotFound {
return orders[id] ?: <http:NotFound>{
body: string `Order not found. Order ID: ${id}`
};
};

resource function get customers/[string customerId]/orders(OrderStatus status = PENDING) returns Order[] {
return from Order entry in orders
where entry.customerId == customerId && entry.status == status
select entry;
};

resource function post orders(Order orderRequest) returns Order|http:BadRequest {
if orders.hasKey(orderRequest.id) {
return <http:BadRequest>{
body: string `Order id already exists. Order ID: ${orderRequest.id}`
};
}
orders.add(orderRequest);
return orderRequest;
}
}

As the next task, let’s connect this Ballerina service with the Megaport’s React front-end application. Let’s define two Javascript functions to handle GET and POST requests.
We will use the React axios library for this task.

import axios from 'axios';

export const postAPI = async ( url, data, config) => {
try {
const response = await axios.post(url, data, config);
return response;
} catch (error) {
throw error;
}
}

export const getAPI = async ( url, config) => {
try {
const response = await axios.get(url, config);
return response;
} catch (error) {
return error;
}
}

And then let’s add these functions to the React functional component to render the relevant data from the Ballerina server.

const Page = () => {
const fetchData = async () => {
setLoading(true);
try {
const response = await getAPI("http://localhost:9090/sales/orders");
if (response.status !== 200) {
setError(response.message);
} else {
setData(response.data);
setLoading(false);
}
} catch (error) {
setError(error.message);
setLoading(false);
}
};
};
export default Page;

So that’s how we connect the Ballerina service with the Megaport’s React application.

The complete source code of this project can be found here.

As we have seen, it is possible to develop full-fledged REST services with fewer lines of code and configurations with Ballerina. Please feel free to join the Ballerina discord server to join with the Ballerina community and visit https://ballerina.io/ to learn more about Ballerina.

References

--

--