Key Takeaways
- Ballerina is an open-source programming language for the cloud that makes it easier to create network applications.
- Ballerina has first-class support for a lot of different network protocols such as HTTP, GraphQL, Websocket, gRPC, etc.
- GraphQL is an application layer server-side technology to optimize network calls.
- Due to the network abstractions, network-aware type system, better support for data representations, and visual diagram support, Ballerina becomes a better programming language to develop GraphQL applications.
In today’s digital transformation era, making applications and web services talk to each other is unavoidable and APIs allow communication between those applications. Various protocols and specifications will result in the API architecture at the end, defining the semantics and syntax of how the messages are passed across the wire.
In this article, we explore how we can expose the data in a MySQL database as an API using GraphQL with the Ballerina programming language. GraphQL is a specification that abstracts the underlying data sources, leaving developers the flexibility to work with data sources such as databases or REST API using any programming language they prefer.
What is GraphQL?
GraphQL is an application-layer, server-side technology developed by Facebook in 2012 and released publicly in 2015 to optimize RESTful API calls. GraphQL can be considered both a query language for the API and a server-side runtime for executing queries using a type system you define for your data.
A GraphQL operation is one of these::
- query (read)
- mutation (write/update)
- subscription (continuous read)
Each of those operations is only a string that needs to be constructed according to the GraphQL query-language specification. GraphQL is not opinionated about the network layer or the payload format, but HTTP and JSON are most commonly used.
Why not REST? GraphQL is the better REST
REST has become the popular architecture when designing APIs over the last decade. REST and GraphQL can be considered two different methods for solving the same problem— accessing data from web services. But, with clients' rapidly changing requirements for access to the APIs, REST APIs were becoming too inflexible. GraphQL was developed to handle the need for more flexible and efficient data-accessing behaviors. Some of the key reasons for choosing GraphQL over REST are listed below.
Avoids overfetching or underfetching
Overfetching means getting more information than you need. This is one of the common limitations of REST, because it always returns fixed data sets from a given endpoint without catering to the clients with specific data needs. Underfetching implies that a specific endpoint doesn’t provide enough required information, so the client will have to make additional requests to fetch everything it needs. But, GraphQL allows you to retrieve the specific information you want in a single API request by defining the exact structure of the information you need using the query syntax.
Allows rapid product iterations on the client-side
Usually, with REST APIs, there are endpoints according to the view required by the client applications. When there is a change in the client application, there is a risk that it will need more or less data than before from a given endpoint. So to cater to the new data needs, REST APIs need to be adjusted. But with GraphQL, because clients can specify exact data requirements, the changes on the client-side can be made without any extra work on the server.
Allows schema and type system based development approach
GraphQL has a strong type system to define the data exposed via a given API, and all these types can be written down in a schema using GraphQL schema definition language (SDL). This schema acts as a contract between the client and the server, and different teams can work on front and backends separately based on the defined schema.
Why use Ballerina?
You can use any popular programming language, such as Go, Java, Node.js, etc., to build your GraphQL applications. We are selecting Ballerina because it provides many value additions due to its design:
- Ballerina is an open-source programming language for the cloud that makes it easier to use, combine, and create network services.
- It’s a modern, industrial-grade, general-purpose language optimized for integrating and writing network services and applications.
- The developer experience is more intuitive due to features such as the network-aware type system, first-class support for network services and resources, built-in support for various technologies, including GraphQL, and sequence diagrammatic syntax.
There are two approaches to designing GraphQL endpoints:
- Schema-first approach: require the GraphQL schema to create a GraphQL service
- Code-first approach: schema is unnecessary, and write the endpoint using code. Schema can be generated as a result.
Ballerina uses the code-first approach to design GraphQL endpoints. The Ballerina GraphQL implementation uses HTTP as the underlying protocol. We explore how each of these features can help you develop the GraphQL application in the next section.
Bookstore use case with GraphQL using Ballerina
The data source for the GraphQL server can be anything such as a database, another API, or a service that holds data. Also, GraphQL can interact with any combination of data sources. This use case shows see how to implement a GraphQL server using the Ballerina language to expose data in a MySQL database as well as data retrieved via another API call.
The MySQL database holds data about a bookstore, including books and authors. Additional information related to books is retrieved using the Google Books API. Clients of the bookstore can do the following operations via the GraphQL API:
- Retrieve the details of all the books
- Retrieve the details of the book by providing the book name
- Add new books to the database
The information sources for the above operations are as follows:
- Title, published year, ISBN number, author name, author country - Retrieved from DB
- Average rating and rating count - Retrieved from Google Books API queried by ISBN of the book
Bookstore with GraphQL server in Ballerina using MySQL database and Google Books API as data sources
All the source code for this sample is available on Github.
Populate the database with sample data
Start by populating the MySQL database with sample data. The Bookstore database has two tables, “Book” and “Author”, with the following fields.
Bookstore database schema
The SQL statements that create the database, tables, and populate data can be found in data.sql. If you save the statements to a file, run the following command to execute them all in the database.
mysql -uroot -p < /path/to/file/data.sql
Implement GraphQL service in Ballerina
Create Ballerina project
Create a Ballerina project to start with the bookstore use case by executing the following command. Refer to Organize Ballerina Code for more details on the Ballerina projects.
bal new bookstore
Created new package 'bookstore' at bookstore.
└── bookstore
├── Ballerina.toml
└── main.bal
1 directory, 2 files
Because this is a service, you don't need the auto-generated `main.bal` file, so it can be deleted.
Create the records
Add a new Ballerina file named `bookservice.bal` to implement the GraphQL service logic. The first step defines the required record types to represent the books and authors data. In Ballerina, a record is a collection of fields of a specific type. The keys are named, and their types define the types of values allowed for the fields. The {| and |} delimiters indicate that the record type allows mapping values, which contain only the described fields.
The following `BookDetails` record represents the details of books retrieved from the database.
type BookDetails record {|
string title;
int published_year;
string isbn;
string name;
string country;
|};
Next, create records to represent the data retrieved from the Google Books API. Before you create the required records, analyze the format of the sample JSON response from the Google Books API request for a given ISBN number. It returns a JSON object with an array named “items”. It has another object named “volumeInfo” and it contains the information about reviews with the field names of “averageRating” and “ratingsCount”.
There are two approaches to working with JSON in Ballerina. You can manipulate JSON values directly using the built-in “json” type or convert the JSON values to application-specific, user-defined subtypes of “anydata”. In the example below, you use the second option by creating records to map the response, because the data-binding support in Ballerina HTTP clients makes it very easy to work with this type of response. Because you are only interested in the rating-related fields, you can create the records only with those fields. Also, you will not use intermediate objects represented by “items” filed, so you can use an anonymous record for that because there is no need to refer to that record type by its name.
type BookReviews record {
record {
VolumeInfo volumeInfo;
}[] items;//Anonymous record defined in-line
};
type VolumeInfo record {
int averageRating?;
int ratingsCount?;
};
Create the Service object
Both the Ballerina record type and service type can be used as Object types in GraphQL. If you use records, each record field is mapped to a field in the GraphQL object, and the type of the record field will be mapped to the type of the corresponding GraphQL field.
If you use service type, each resource method inside a service type represents a field of the object. The resource methods in these service types can have input parameters. These input parameters are mapped to arguments in the corresponding field.
Using a record type as an object has limitations, hence, you will use a service type to represent the `Book` object in this use case.
Within the service `BookDetails` is a final and read-only field, which means it cannot be assigned after it has been initialized.
The resources in Ballerina GraphQL services can have hierarchical resource paths. When a hierarchical path is present, such as author/… below, an object type is created for each intermediate path segment with the same name. Every subpath under a path segment will be added as a field of the created type.
service class Book {
private final readonly & BookDetails bookDetails;
function init(BookDetails bookDetails) {
self.bookDetails = bookDetails.cloneReadOnly();
}
resource function get title() returns string {
return self.bookDetails.title;
}
resource function get published_year() returns int {
return self.bookDetails.published_year;
}
resource function get isbn() returns string {
return self.bookDetails.isbn;
}
resource function get author/name() returns string {
return self.bookDetails.name;
}
resource function get author/country() returns string {
return self.bookDetails.country;
}
resource function get reviews() returns VolumeInfo|error {
string isbn = self.bookDetails.isbn;
return getBookReviews(isbn);
}
}
Create the GraphQL service
Now, you write the GraphQL service. Because Ballerina has first-class support for network-based interactions, writing services are straightforward. Service objects support network interaction using remote methods and resource methods. Listeners provide the interface between the network and service objects.
First, you need to import the `ballerina/graphql` module. Then, you can create a GraphQL listener object by providing the port to which it is listening, and can attach it to the service.
Resources enable a data-oriented approach to expose services in a RESTful way, whereas remote methods expose services in a procedural manner to interact with remote network services. Ballerina services can have resource methods and remote methods; n Ballerina, the GraphQL query type is represented by resource methods, while remote methods represent the mutation type.
The next step is to add the required functionality as remote functions or resource functions. The functions `allBooks` and `bookByName` are resource functions to retrieve book data via GraphQL queries; hence they return the `Book` array. To add new books to the database, use GraphQL mutation via the `addBook` remote method. It has the book's details as input parameters and returns an int value which is the index of inserted book or -1 if an error occurs.
Ballerina GraphQL Service outline
The next step is to implement the actual data access logic so that you can fill the implementation of the remote and resource methods.
The complete code can be found on bookservice.bal and service code is as follows. Refer to the next section for more details on the `getBooks`, `addBookData`, and `getBookReviews` functions related to the DB interactions and the API call.
service /bookstore on new graphql:Listener(4000) {
resource function get bookByName(string title) returns Book[] {
return getBooks(title);
}
resource function get allBooks() returns Book[] {
return getBooks(());
}
remote function addBook(string authorName, string authorCountry, string title,
int published_year, string isbn) returns int {
int|error ret = addBookData(authorName, authorCountry, title, published_year, isbn);
return ret is error ? -1 : ret;
}
}
Implement the data access logic
Because this example uses a MySQL database as the backend data store, you need to write functionality to query the database and add new records. Ballerina provides first-class support for DB interactions.
Now, add another file named `bookdatastore.bal` to the project to have the DB and API call related implementations. First, you need to create a MySQL database client, and import the `ballerinax/mysql`, `ballerina/sql`, and `ballerinax/mysql.driver` modules.
Although you can provide the configuration values directly when initializing the client, this example uses Ballerina configurability support to provide the configurations. This approach enables users to modify the system behavior through external user inputs depending on the environment, and sensitive data like passwords are not exposed via the code. Once you define the configurable record for database configs, you can create the DB client as follows.
final mysql:Client dbClient = check new (database.host, database.username, database.password, database.name, database.port);
Then config values can be provided via the Config.toml file in the project.
Now, add an HTTP client to fetch the required data from the Google Books API. You need to import the `ballerina/http` module. Create the client as follows.
final http:Client bookEp = check new ("https://www.googleapis.com/books/v1");
With this, you have completed the implementation of the scenario. The complete source code for database access can be found in bookdatastore.bal. Use the generated diagrams
Integration use cases are inherently complex due to the interaction between multiple entities. So understanding the overall process and the sequence is essential to maintain, improve, and explain the scenario. Ballerina has built-in diagram capabilities, which generate a complete sequence diagram out of the code as you write it. This diagram acts as documentation of the code and makes it easier to comprehend the program than it might be from reading the source code. You can view and edit these diagrams using the Ballerina VSCode plugin.
Below is the diagram for the `getBooks` method. Similar diagrams exist for other methods, and you can easily expand, view, and edit using the VSCode plugin.
Ballerina Diagram for the source code
Run the Bookstore service
Now, let’s run and test the GraphQL service. To run the service, execute the below command within the root folder of the `bookstore` project:
bal run
If you connect to this service using any GraphQL client tools, it will show the following schema:
type Query {
allBooks: [Book!]!
bookByName(title: String!): [Book!]!
}
type author {
country: String!
name: String!
}
type VolumeInfo {
averageRating: Int
ratingsCount: Int
}
type Book {
reviews: VolumeInfo!
published_year: Int!
author: author
isbn: String!
title: String!
}
type Mutation {
addBook(
authorName: String!
authorCountry: String!
title: String!
published_year: Int!
isbn: String!
): Int!
}
Test the Bookstore service
To call the GraphQL server, you need to use a client. You can do this from the command line with curl by sending an HTTP POST request to the endpoint, passing the GraphQL query as the query field in a JSON payload. Also, you can use the Ballerina GraphQL client tool to generate a client in Ballerina using a given GraphQL schema (SDL) and GraphQL queries. If you prefer to use a graphical user interface, use clients such as GraphiQL or Altair.
For all the requests the endpoint is : http://localhost:4000/bookstore.
Sample Request 1: Get the titles of all books
GraphQL query:
{allBooks {title}}
Response:
{
"data": {
"allBooks": [
{ "title": "Pride and Prejudice" },
{ "title": "Sense and Sensibility" },
{ "title": "Emma" },
{ "title": "War and Peace" },
{ "title": "Anna Karenina" }
]
}
}
CURL command to request the same:
curl -X POST -H "Content-type: application/json" -d '{ "query": "{allBooks {title}}" }' 'http://localhost:4000/bookstore'
Sample Request 2: Get more details of all books
This is where the true power of GraphQL shows. Users can request the exact information they need in the format they prefer without having different endpoints, just by changing the query. You can see some of the ratings are “null” here becaus those values are not returned by the JSON response of the Google books API call.
GraphQL query:
{allBooks {title, author{name}, reviews{ratingsCount, averageRating}}}
Response :
{
"data": {
"allBooks": [
{
"title": "Pride and Prejudice",
"author": {
"name": "Jane Austen"
},
"reviews": {
"ratingsCount": 1,
"averageRating": 5
}
},
{
"title": "Sense and Sensibility",
"author": {
"name": "Jane Austen"
},
"reviews": {
"ratingsCount": 3,
"averageRating": 4
}
},
{
"title": "Emma",
"author": {
"name": "Jane Austen"
},
"reviews": {
"ratingsCount": null,
"averageRating": null
}
},
{
"title": "War and Peace",
"author": {
"name": "Leo Tolstoy"
},
"reviews": {
"ratingsCount": 5,
"averageRating": 4
}
},
{
"title": "Anna Karenina",
"author": {
"name": "Leo Tolstoy"
},
"reviews": {
"ratingsCount": 1,
"averageRating": 4
}
}
]
}
}
CURL command to send the same request:
curl -X POST -H "Content-type: application/json" -d '{ "query": "{allBooks {title, author{name}, reviews{ratingsCount, averageRating}}}" }' 'http://localhost:4000/bookstore'
Sample Request 3: Get details of books with input parameter
GraphQL Query:
{bookByName(title: "Emma") {title, published_year}}
Response:
{ "data": { "bookByName": [{ "title": "Emma", "published_year": 1815 }] } }
CURL Command to send the same request:
curl -X POST -H "Content-type: application/json" -d '{ "query": "{bookByName(title: \"Emma\") {title, published_year}}" }' 'http://localhost:4000/bookstore'
Sample Request 4: Mutation to insert data into the database
GraphQL Query:
mutation {addBook(authorName: "J. K. Rowling", authorCountry: "United Kingdom", title: "Harry Potter", published_year: 2007, isbn: "9781683836223")}
Response:
{
"data": {
"addBook": 6
}
}
curl -X POST -H "Content-type: application/json" -d '{ "query": "mutation {addBook(authorName: \"J. K. Rowling\", authorCountry: \"United Kingdom\", title: \"Harry Potter\", published_year: 2007, isbn: \"9781683836223\")}" }' 'http://localhost:4000/bookstore'
CURL Command to send the same request:
Wrapping up
GraphQL can be a better choice than REST in modern application development efforts. With first-class support for network-related abstractions, the Ballerina programming language provides simple, yet powerful, ways of writing GraphQL services. In our example, we implemented a sample GraphQL use case for a bookstore, combining multiple backend data sources including a MySQL database and the Google Books API.
You can learn more about Ballerina by visiting ballerina.io.
You can engage with Ballerina GraphQL module development efforts by checking issues.
The full source code of this Bookstore project can be found here.
For more information
Learn more about Ballerina GraphQL by referring to the following resources: