Ballerina for Full Stack Developers
Learn why you should consider Ballerina for backend development in your next full stack project
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
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.
- MEAN — MongoDB 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.
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.
- Developing a BFF — Acts as an adapter between the frontend UX and the backend microservices.
- 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 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 aremote 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.
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:
Let’s decode each part of the syntax here:
import ballerina/http;
— Imports theballerina/http
packageservice /hello on new http:Listener(8080)
— Creates a service with the context path/hello
on anHTTP
listener listening on port8080
. The type of the service is determined by the type of thelistener
attached. In this case, since our listener is anHTTP
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 theHTTP 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 astring
. 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:
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 theport
is configurable meaning it can be configured at the runtime. As defined inconfigurable int port = 8080;
, theport
has a default value of8080
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 namedamount
is required now. This resource method returns either anHTTP OK
or anerror
(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 recordConversionResponse
. 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.
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.
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 theaudience
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.
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.
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.
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.
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.