Data Sharing Using Graphql: Chaiporn Jaikaeo Department of Computer Engineering Kasetsart University
Data Sharing Using Graphql: Chaiporn Jaikaeo Department of Computer Engineering Kasetsart University
Data Sharing Using Graphql: Chaiporn Jaikaeo Department of Computer Engineering Kasetsart University
Chaiporn Jaikaeo
Department of Computer Engineering
Kasetsart University
Revised 2020-11-04
Outline
• Limitations of REST APIs
• GraphQL concept
• GraphQL wrapper for OpenAPI
2
Downsides of REST APIs
• Multiple endpoints exposed to clients
• Multiple round trips by clients
• Under/over-fetching of data
GET /basins/3/stations
[{'id':12},{'id':45}]
GET /stations/12
{'id':12,'name':'a','lat':13.8,'lon':101.5}
GET /stations/45
Client {'id’:45,'name':'b','lat’:10.5,'lon’:103.9}
Server
3
GraphQL
• Graph Query Language, developed by Facebook
• Data are modeled as a graph
• APIs are organized around types and fields,
not endpoints
• Clients see APIs similar to Object-Oriented Programming
• Since GraphQL only defines the interface, any backend
can be used
GraphQL
REST GraphQL RPC REST RPC
Business Logic Layer Business Logic Layer
Persistence Layer Persistence Layer
(e.g., databases) (e.g., databases)
4
GraphQL Schema
• Model data as a graph by defining a schema, containing different
types of nodes and how they link to one another
type Query { { {
basin(basinId: Int!): Basin basin(basinId: 3) { "data": {
root type basins: [Basin] basinId "basin": {
} name "basinId": 3,
stations { "name": "Ping",
type Basin { stationId "stations": [
basinId: Int name {
area: Float } "stationId": 327301,
name: String } "name": "MAE JO AGROMET."
stations: [Station] } },
} {
Query "stationId": 327501,
type Station { "name": "CHIANG MAI"
stationId: Int }
name: String ]
basin: Basin }
lat: Float }
lon: Float }
}
Result
Schema
(can also be expressed in other languages)
5
GraphQL and OpenAPI
• OpenAPI is designed for RESTful APIs
• OpenAPI-to-GraphQL is a tool developed by IBM to create
a GraphQL wrapper around existing OpenAPI-based APIs
◦ Shown to work with most existing OpenAPI specifications
(though not 100%)
https://github.com/IBM/openapi-to-graphql/blob/master/docs/tutorials/watson.md 6
Example: Rainfalls GraphQL API
• We will create a simple GraphQL wrapper around the REST
API using OpenAPI-to-GraphQL
• Clone/download example code from
◦ https://gitlab.com/cjaikaeo/rain-graphql-daq-2020f
• Root path is /rain-api/v2
◦ Slightly modified from the previous v1 spec
• All REST endpoints have already been implemented
7
Running the Example (1)
• Inside the project folder, create and activate a virtual
environment
python3.9 -m venv env
. env/bin/activate # macOS and Linux
env\Scripts\activate.bat # Windows
May type them all in one line For Windows’ command prompt, use the ^ character
without the \ character instead of \ to continue on the second line
8
Running the Example (2)
• Create config.py from config.py.example
• Start the REST API server
python app.py
9
GraphiQL
• A graphical interactive in-browser GraphQL IDE
Query result
GraphQL query
Schema
documentation
10
OpenAPI-to-GraphQL: Schema Mapping
• GraphQL interface is created around data definitions
(i.e., schema), not endpoints
paths: {
/basins/{basinId}: basin(basinId: 3) {
parameters: name
- name: basinId area
in: path }
required: true }
schema:
type : integer
components:
get:
operationId: controller.get_basin_details schemas:
responses: Basin:
200: type: object
properties:
description: Success
basinId:
content:
application/json: type: integer
schema: name:
$ref: '#/components/schemas/Basin' type: string
area:
type: number
11
Basic Query Examples
{
basins {
name Get all basins’ names
}
}
{
basins {
name
area Get all basins’ names and their areas
}
}
{
basin(basinId:3) {
name Get the name and area of the basin
}
area
whose ID is 3
}
12
OpenAPI-to-GraphQL: Links
• Links (new in OpenAPI 3.0) are used to create nested data structures
paths: {
/basins/{basinId}: basin(basinId: 3) {
parameters:
name
- name: basinId
in: path area
required: true stations {
schema: name
type : integer lat
get: lon
operationId: controller.get_basin_details
}
responses:
200: }
description: Success }
content:
application/json:
schema:
$ref: '#/components/schemas/Basin'
links:
stations:
operationId: controller.get_stations_in_basin
parameters:
basinId: $response.body#/basinId
13
OpenAPI-to-GraphQL: Nest Queries
• A nested query gets translated into multiple REST API requests
• Responses are then combined into a single GraphQL response
{ {
basin(basinId: 3) { "data": {
"basin": {
name
area
1 GET /rain-api/v2/basins/3 "name": "Ping",
"area": 37211.9,
stations { "stations": [
name {
lat "name": "MAE JO AGROMET.",
2 GET /rain-api/v2/stationsInBasin/3 "lat": 18.7833,
lon
"lon": 98.9833
}
},
} {
} "name": "CHIANG MAI",
"lat": 18.79,
"lon": 98.9769
}
]
}
}
}
14
Example: Rainfalls API with Links
• The folder openapi contains rain-api-with-links.yaml
◦ Similar to rain-api.yaml, with links added
15
Running the Example with Links
• Start the REST API server
python app-with-links.py
◦ Optionally test the API at http://localhost:8080/rain-api/v2/ui
Notes: you may put all the above in a single line without \
16
Nested Query Examples
{
basins { Get all basins’ names and the names of
name
stations { name lat lon } all stations inside, along with their
}
}
latitude and longitude
{
basins {
name Get all basins’ names and their annual
}
annualRainfall(year:2000)
rainfalls in the year 2000
}
17
Exercise: Monthly Rainfalls
• Add a new link to in openapi/rain-api-with-links.yaml so
that average monthly rainfalls is nested inside Basin objects
◦ Name the nested field avgMonthlyRainfalls
◦ Link to the endpoint /rain-api/v2/basins/{basinId}/monthlyAverage
• Your new spec must be able to run this query through OpenAPI-to-
GraphQL
{
basins {
name
avgMonthlyRainfalls {
month
amount
}
}
}
18
Conclusion
• GraphQL is a query language designed to provide a single
API endpoint for retrieving all and only needed data in a
single request
• Nested data structures are used to avoid multiple requests
• OpenAPI-to-GraphQL is a tool for creating a simple
GraphQL wrapper around an existing OpenAPI specification
• To support nested queries, OpenAPI’s links are heavily
utilized by OpenAPI-to-GraphQL
19
Further Reading
• Links in OpenAPI 3
◦ https://swagger.io/docs/specification/links/
• GraphQL - A query language for your API
◦ https://graphql.org/
• IBM’s OpenAPI-to-GraphQL Project
◦ https://developer.ibm.com/open/projects/openapi-to-graphql/
◦ https://github.com/IBM/openapi-to-
graphql/tree/master/packages/openapi-to-graphql
• Erik Wittern, Alan Cha, and Jim A. Laredo. Generating GraphQL-
Wrappers for REST(-like) APIs
◦ https://arxiv.org/pdf/1809.08319.pdf
20
Assignment 9.1: All-years Annual Rainfalls
• Modify openapi/rain-api-with-links.yaml to create a new REST endpoint
that returns a list of annual rainfalls in all available years for a specified basin
◦ Endpoint URL: /rain-api/v2/basins/{basinId}/allAnnualRainfalls
◦ Return: list of AnnualRainfall objects, already defined in the schemas section
21
Assignment 9.1's Expected Results
• Result from /rain-api/v2/basins/2/allAnnualRainfalls
[
{
"amount": 1055.12,
"year": 1987
},
{
"amount": 1309.64, {
"year": 1988 "data": {
}, "basin": {
: "name": "Ping",
] "allAnnualRainfalls": [
{
"year": 1987,
• Result from GraphQL query "amount": 1055.12
},
{ {
basin(basinId:3) { "year": 1988,
name "amount": 1309.64
allAnnualRainfalls { },
year :
amount ]
} }
} }
} }
22