Ballerina for Full Stack Developers

Learn why you should consider Ballerina for backend development in your next full stack project

Imesha Sudasingha
16 min readApr 16, 2022
Photo by Olaf Val on Unsplash

Note: This is a slight variation of the article I wrote for InfoQ under the title “Ballerina for Full-Stack Developers: a Guide to Creating Backend APIs”.

TLDR;

In this article, we will look at the Ballerina programming language’s rich support for writing backend APIs for your frontends. We will be looking at,

  • “Stacks” used for full stack development
  • Selecting a programming language/technology to write backend APIs
  • Inbuilt networking primitives of Ballerina
  • Writing REST (explained in depth), GraphQL (an overview) and WebSocket (an overview) APIs in Ballerina
  • Securing REST APIs, authentication and authorization with Ballerina
  • Observability — Metric monitoring, logging and distributed tracing with Ballerina
  • Other key language features of Ballerina

At the end of this article, you will have a good understanding on why Ballerina is a prominent candidate to write your next backend API.

Background

A web app consists of 3 layers in the simplest form. Namely, the client side (frontend), server side (backend) and the persistence layer (database). Full stack development refers to the practice of developing all 3 layers of a web application.

  • Frontend development involves HTML, CSS and vanilla JavaScript or one or more JavaScript frameworks/libraries (like JQuery or React.js). Available tools, frameworks and libraries have grown exponentially over the past few years. Therefore, frontend development itself is a very broad subject now.
  • Backend development usually involves a serverside scripting languag (like PHP or JSP) e or a service consumed by the frontend. With the emergence of single page applications (SPA), developers have moved away from traditional server side scripting languages and embraced APIs (REST, GraphQL, WebSocket, etc) as the backend.
  • Persistence layer includes one or more SQL or NoSQL databases. But with the emergence of “Big Data”, data storage is no longer limited to conventional data stores.

Popular Stacks

The MERN Stack

Out of all the technologies used under each layer mentioned above, there were technologies more popular than the others. When such technologies that cover all 3 layers were combined, we called “a stack”. We have seen several popular stacks over the years.

  • LAMP /LEMP — JavaScript for the frontend. Linux for hosting, Apache/Nginx as the web server, MySQL as the persistence layer and PHP as the backend (or server side) language. Frameworks like Laravel, and Symphony were popular under this stack.
  • MEANMongoDB for the data persistence layer, Express as the backend framework, AngularJS as the frontend JavaScript framework and NodeJS as the server runtime.
  • MERN — Same as MEAN stack, except that ReactJS had been chosen over Angular.
  • Ruby on Rails — A web application framework written in Ruby.
  • Django — JavaScript for the frontend, Python Django framework for the backend and a suitable SQL or NoSQL database layer.

However, the stacks are not that simple anymore. For a given layer, available alternative technologies have multiplied. Within a given layer, number of different technologies we can combine to develop the end product have also multiplied.

Modern Full Stack Development

When it comes to full stack development, SPA (single page applications) and PWA (progressive web applications) are becoming the norm and concepts like SSR (server side rendering) has emerged to address limitations in them. These web applications (frontend) is supposed to work with a backend API (REST, GraphQL, etc) to provide the ultimate functionality to the end users. With that, concepts like BFF (Backend for frontend) have emerged to align the backend APIs with the frontend user experience (UX).

BFF — Backend for Frontend

An organization can have a number of microservices whose services are used by different parties like mobile applications, web applications, other services/APIs and external parties. Modern web applications however need a tightly coupled API to work closely with the frontend UX. Therefore, A BFF acts as the interface between the frontend and the microservices.

A BFF invoking multiple downstream services to construct a view in the frontend. Downstream APIs can be of different types (REST, GraphQL grpc, etc)

Read Pattern: Backends For Frontends for an in depth understanding on BFF architecture pattern.

With the above concepts in mind, let’s discuss a bit more about modern web development.

Backend Development in the context of Full Stack Development

Developing backend APIs can mean 2 things.

  1. Developing a BFF — Acts as an adapter between the frontend UX and the backend microservices.
  2. Developing microservices — Developing the individual microservices which will be consumed directly or indirectly (via BFF) by a frontend.

In the context of full stack development we will only consider the backend APIs which are directly being invoked by a frontend. Those APIs can be written as BFF APIs or as separate microservices.

Choosing the “Stack”

Nowadays, developers don’t just go for a stack just because its popular. They choose the best frontend technologies to match the UI/UX they wish to implement. Then they choose the backend technologies considering several factors including the time to market, maintainability and developer experience.

In this article I’m going to introduce a new promising candidate for backend development, Ballerina. In future, you can consider it while evaluating backend technologies for your full stack journey.

What is Ballerina?

Ballerina Programming Language Logo (https://ballerina.io)

Ballerina is an open-source cloud-native programming language developed to make consuming, combining, and creating network services easier. Ballerina Swan Lake, the next major version of Ballerina language was released this year with major improvements to all aspects including an improved type system, enhanced SQL-like language-integrated queries, enhanced/intuitive service declarations, and many more.

The main motive behind ballerina is to enable developers to focus on business logic while spending less time on integrating cloud-native technologies. The intuitive way of defining and running services on the cloud using Ballerina’s inbuilt network primitives plays a key role in this revolution. Flexible type system, data-oriented language-integrated queries, enhanced and intuitive concurrency handling along with inbuilt observability and tracing support, make Ballerina one of the go-to languages for the cloud era.

When developing APIs which are being invoked directly by the frontend, we have several popular choices:

  • REST APIs
  • GraphQL APIs
  • Web Sockets

Once a suitable API type is chosen, next we have to consider the following factors.

  • Secure Communication
  • Authentication
  • Authorization
  • Monitoring, Observability and Logging

In addition to the above factors, we have to consider the maintainability and developer experience when developing the APIs. Let’s have a look at how Ballerina provides support developing above mentioned API types and what makes Ballerina a promising candidate for backend API development.

Ballerina and Network Interactions

One of the main objectives of Ballerina programming language is to simplify writing network interactions. With that in mind, Ballerina has networking primitives inbuilt to the language. While all the other mainstream programming languages treat networking as just another I/O resource, Ballerina has first class support for network interactions. To facilitate that, Ballerina has the following first class components:

  • Listeners — Acts as the interface between the networking layer and the service layer. A listener represents the underlying transport like HTTP, grpc, TCP or Web Socket.
  • Services — Represents a service which exposes an organization’s capabilities to the end users. HTTP, GraphQL, grpc and web socket are few examples to such services.
  • Resource Methods — Represents a unit of functionality within a service. For example, if we consider a simple CRUD service to manage inventory, add inventory is represented by a single resource method while delete inventory operation is represented by another.
  • Clients and Remote Methods — Writing a service these days usually includes invoking one or more external or internal services. For example,
    1. Within one of your services, you may want to send an email. For that, you need an email client.
    2. Same service may have a requirement to invoke one or more internal grpc services. For that, you need grpc clients.
    Similarly, writing services definitely require invoking external services. For that, Ballerina has a rich concept called clients and an external call is represented there by a remote method . Invoking a remote method in the Ballerina runtime is asynchronous (non-blocking, while no need for explicit callbacks or listeners).

These language inbuilt networking primitives coupled with other language features like explicit error handling, inbuilt json/xml support and being flexibly typed help developers write intuitive and maintainable network interactions faster. This enables developers and the organizations in turn to focus more on innovation.

Ballerina lang’s features at a glance
Ballerina lang’s features at a glance (https://ballerina.io)

Let’s explore how we can write intuitive and meaningful backend APIs with Ballerina using its support for REST and GraphQL APIs. Please follow the getting started guide to install and setup Ballerina.

Developing REST APIs

Let’s have a look at how to write REST APIs with Ballerina.

Say “Hello World!”

A hello world REST API written in Ballerina looks like below:

A hello world service in Ballerina

Let’s decode each part of the syntax here:

  • import ballerina/http; — Imports the ballerina/http package
  • service /hello on new http:Listener(8080) — Creates a service with the context path /hello on an HTTP listener listening on port 8080 . The type of the service is determined by the type of the listener attached. In this case, since our listener is an HTTP listener, this becomes an HTTP service.
  • resource function get world() returns string — Represents a single operation which can be performed over this HTTP service. get is the “resource accessor”. In simple terms, it represents the HTTP Method (get, post, delete, etc). world is the name of the function and the function name becomes the path. It means the resource path/hello/world is handled by this resource method. returns string means that this service returns a string . We can return complex objects here also.
  • return “Hello World!”; — Represents the return value of the resource method. It’s the string “Hello World!”.

Following diagram shows an overview of the Ballerina HTTP service syntax:

Ballerina HTTP Service Structure (Source: https://medium.com/ballerina-techblog/http-deep-dive-with-ballerina-services-7a6e69af2fbb)

To get an in depth understanding of the Ballerina HTTP service syntax, especially how to use query and path parameters, payload data binding and etc, please refer to the following article:

An integration example — A Currency Conversion API

Following is a bit more complex REST API. Given a baseCurrency , targetCurrency and an amount , this API returns the converted amount. To get the latest exchange rates, this API uses an external service.

Compared to the hello world example, this one demonstrates few more interesting features of Ballerina.

  • service / on new http:Listener(port) — Base path is / now. And the port is configurable meaning it can be configured at the runtime. As defined inconfigurable int port = 8080; , the port has a default value of 8080 and is configurable. Configurable variables is a noteworthy feature too.
  • resource function get convert/[string baseCurrency]/to/[string targetCurrency](float amount) returns http:Ok|error —In this case, the resource path is /convert/{baseCurrency}/to/{targetCurrency} and a query param named amount is required now. This resource method returns either an HTTP OK or an error (which is mapped to 500 — Internal Server Error).
  • ConversionResponse response = check dccClient->get(string `/latest?base=${baseCurrency}`); — Invoke the external exchange rate API and converts the response to the open record ConversionResponse . This call is non-blocking. The response payload is seamlessly converted to an open record demonstrating Ballerina’s Open by Default principle.

Once run, a curl request like below will convert 100 USD to GBP.

curl http://localhost:8080/convert/USD/to/GBP?amount=100

Bonus: Low Code Development

Ballerina has a leak free graphical representation. That is, you can edit the source code and the low-code view (graphical representation) simultaneously. Following is the low-code view of the above API.

Low-code view of the above currency conversion API.

Even though we will not be exploring the low code aspect of Ballerina much, it is ideal for non-technical or less-technical people to understand and write code. Give it a try as well.

Leak Free — anything can be programmed in code and everything in code is visual.

A Simple CRUD Service

Following is an example CRUD service written in Ballerina. It manipulates a set of products which are kept in memory.

An in memory product service containing CRUD operations

While most of the syntax are self explanatory, this service has 4 resource methods:

  • List all products — GET /products
  • Add a new product — POST /products
  • Update a product — PUT /products
  • Delete a product — DELETE /products/{id}

Note how the types have been defined to represent products, price and currency. Then we have defined response types where necessary to have a desired schema. Created is to represent the response of adding a product. BadRequest is to represent an error in validation.

public type BadRequest record {|
*http:BadRequest;
ErrorResponse body;
|};
public type Created record {|
*http:Created;
Headers headers;
anydata body = null;
|};

Having such schema helps developers understand the code easily. Just by looking at the resource method definition, developers can get a clear overview of the resource method. What’s the resource path, what query/path parameters are required, what’s the payload and what are the possible return types.

resource function post .(@http:Payload Product product) returns Created|BadRequest { }

It’s a POST request, sent to the /products (derived by looking at the service based path and resource method name), requires a payload of type Product and returns either a bad request error (400) or an HTTP CREATED response (202).

Generating the OpenAPI Specification

Once we have the service written in Ballerina, you can simply generate the OpenAPI specification just by pointing to the source file. It will output the OpenAPI specification with corresponding status codes and schema by looking at the source.

You can read more on that under the OpenAPI section:

Generating a complete OpenAPI spec helps you to generate required clients. In our case, to generate JavaScript clients and integrate our frontend with the backend easily.

Securing Services

You can secure your service updating the HTTP listener to an HTTPS listener as follows.

listener http:Listener securedEP = new(8080,
secureSocket = {
key: {
certFile: "../resource/path/to/public.crt",
keyFile: "../resource/path/to/private.key"
}
}
);
service /hello on new secureEP {
resource function get world() returns string {
return "Hello World!";
}
}

You can enable mutual SSL and do advanced configurations as well. Refer to the Ballerina examples on HTTP service security for more information.

Authentication

Ballerina has inbuilt support for 3 authentication mechanisms.

JWT

You can either provide the cert file or the JWKs endpoint URL of your authorization server and enable JWT signature validation. For example, if we are to protect our service with an IDaaS (Identity as a Service) like https://asgardeo.io, we just have to add the following annotation to the service:

@http:ServiceConfig {
auth: [
{
jwtValidatorConfig: {
signatureConfig: {
jwksConfig: {
url: "https://api.asgardeo.io/t/imeshaorg/oauth2/jwks"
}
},
}
}
]
}

Additionally,

  • You can protect the entire service or only a subset of resource paths.
  • You can validate the issuer or the audience in the JWT
  • You can perform authorization based on claims (explained later)

Refer the REST API Security section of Ballerina examples to learn more.

OAuth2

Similarly to JWT, you can protect your services with OAuth2.

Basic Auth

For basic auth, 2 user store options are available; file and LDAP. Please refer the following examples to see how it is done:

Authorization

With OAuth2 and JWT you can validate scopes per service or per resource. In both cases, you can specify a custom scope key. The default is scope .

In case of JWT, you can use a custom claim containing user roles (role based access control — RBAC) or permissions (fine grained access control) to authorize individual operations.

As shown above, the /products service validates if the incoming JWT has the product:view permission to list products and the product:create permission to create a product. The scopeKey is set to permissions which is the name of the claim in the JWT to look at for validation. Additionally it validates the issuer and the audience .

GraphQL

In order to keep this article short, I’m not going to go in depth on writing GraphQL APIs. However, similarly to the REST APIs, Ballerina has the same level of support for GraphQL services. Please refer to the following links to read more on this.

Read more on GraphQL in the Reference by Example section of the Ballerina website for more information on writing GraphQL services.

Web Sockets

I will not be discussing this in depth as well. Please refer to the WebSockets and WebSocket security sections under the Reference by Example section of the Ballerina website for more details on writing WebSocket services.

Observability

Observability is one of the key features inbuilt to the language. With Ballerina, you can perform distributed tracing and metric monitoring out of the box.

Tracing

Distributed tracing is available via Jaeger and Choreo. In order to publish traces to Jaeger (or Choreo), you just have to add an import to your code. During runtime, your services will then publish traces to Jaeger (or Choreo) using the Open Telemetry standards.

Distributed tracing with Jaeger and Ballerina

Logging

The logging framework provided by Ballerina is ideal for log analysis using logstash and similar log analyzers. While writing code, you can pass additional key value pairs to the log line.

log:printInfo("This is an example log line", key1 = "key1", payload = { id: 123});

The output of the above log line looks like:

time = 2022-01-26T16:19:38.662+05:30 level = INFO module = "" message = "This is an example log line" key1 = "key1" payload = {"id":123}

Metrics

Realtime metrics can be monitored with Prometheus and grafana. Additionally you can monitor live metrics using Choreo as well.

Observability view in Choreo (source: https://wso2.com/choreo/resources/observability-in-choreo-the-new-low-code-development-platform/)

Similarly to distributed tracing, you just have to add an import to your source code and import a ready made grafana dashboard to publish and monitor realtime metrics.

Realtime metrics Grafana dashboard for Ballerina

Read more on Ballerina observability features from the following links:

Persistence Layer

Next major aspect in backend development is the persistence layer. Ballerina has a rich list of SQL and NoSQL DB clients.

DB calls made via clients are non-blocking in Ballerina

SQL

Following clients are available as of now:

Additionally, Ballerina comes with a very convenient way of writing prepared statements using RawTemplates.

SQL queries written with raw templates

In the above example, ${<variableName>} represents variables bound to the query and the above piece of code is executed as a prepared statement in the runtime.

Data Binding and Streams

Similarly, we can fetch data as a stream of a user defined type using select queries as follows. Suppose we have a Product record as below:

type Product record {|
int id?;
string productName;
float price;
string currency;
|};

And the product table is defined as below:

CREATE TABLE `products` (
`id` int NOT NULL AUTO_INCREMENT,
`product_name` varchar(255) NOT NULL,
`price` float DEFAULT NULL,
`currency` varchar(5) DEFAULT NULL,
PRIMARY KEY (`id`)
)

You can fetch data as a stream of Product records as below:

stream<Product, error?> productStream = mysqlClient->query(`select id, product_name as productName, price, currency from products`);

Note that the record field names and fetched column names are similar.

NoSQL

On top of the usual benefits of NoSQL, Ballerina’s inbuilt JSON and open record types come in handy when working with unstructured or semi-structured data.

Note: Ballerina’s is still in it’s early stages. Therefore, a fully capable ORM library is not yet available.

Other Noteworthy Features

Inbuilt JSON/XML Support

As we have already seen, Ballerina has inbuilt support for popular wire formats, JSON and XML. With Ballerina, you can seamlessly convert between user defined types and JSON as we saw in HTTP services and DB examples.

Structural Type System

Instead of nominal typing (as in Java/Kotlin), Ballerina relies on the shape of the construct (like in Go/TypeScript) to determine subtypes. This allows developers to seamlessly convert among user defined types and among user defined types and JSON.

Statically Typed

Ballerina is statically typed. This enables Ballerina to provide a rich set of tools to write reliable and maintainable code. At the same type, Ballerina follows the “open by default” principle where only have to define what you are interested in. Open records is one example to this usage.

Explicit Error Handling

Errors should be handled explicitly. As we saw in examples, client calls return a union of the result and an error . Developers should type check for errors and handle them explicitly.

Null Safe

Ballerina is null safe

Maintainable

With all the above aspects combined, Ballerina becomes a maintainable and reliable language tailored for network programming.

Graphical

As we have seen briefly, Ballerina has a really good, leak-free low code aspect. Ballerina use sequence diagrams to visualize network interactions. This is really good for less technical and non-technical people to program and to understand a program.

Summary

In this article, I wanted to give a brief, but comprehensive overview of the Ballerina programming language’s support for writing backend APIs. We covered how we can write REST APIs in depth. And we looked at how to secure services, how to perform authentication and authorization and generating OpenAPI specifications.

Next we looked briefly at how we can write GraphQL and WebSocket services. Then we looked at the observability features and the persistence layer support (for SQL and NoSQL databases). Finally, we looked at few of the Ballerina language features which are noteworthy.

I think the discussed content will be helpful for you to explore the Ballerina programming language more. And to understand its prominence in the context of backend API development. I hope I was able to establish the fact that, Ballerina is a true cloud native programming language which treats network primitives as first class citizens. I invite you to explore more on the Ballerina programming language.

Thank you for following this article. Feel free to share your opinions and suggestions. Also, reach out to me or the Ballerina community if you have further questions.

Read about Ballerina’s inbuilt SQL-like query expression support in Uncovering Interesting 2020 Olympics Stats with Ballerina Language-integrated Queries.

--

--

Imesha Sudasingha

Co-Founder @HighFlyer | Ex @WSO2 | Ex @BallerinaLang | Opensource | Member @TheASF