Build a TypeScript SDK with Pagination
This tutorial includes the following SDK languages and versions:
TypeScript Java Python C# Go PHP ✅ ❌ ❌ ❌ ❌ ❌
Pagination is crucial for managing large datasets, as it allows for efficient data retrieval by splitting the data into smaller, more manageable chunks. The offset-limit pagination approach uses two parameters:
- Limit: Specifies the number of items to return per page.
- Offset: Indicates where the data should start, skipping a defined number of records.
Implementing offset-limit pagination involves complex query configurations. Instead of configuring complex queries, liblab’s solution lets you handle pagination effortlessly. You can either:
- Automatically iterate through all data, receiving results in manageable pages.
- Use the
next()
function to navigate page by page, fetching the next data set only when needed.
This tutorial covers two methods for adding pagination to a TypeScript SDK using liblab’s solution:
- Configuring the OpenAPI spec with the
x-liblab-pagination
annotation. - Customizing the SDK through the
liblab.config.json
file.
Prerequisites
- A liblab account.
- The liblab CLI installed.
liblab CLI supports multiple package managers. Check the Getting started overview for more details.
Steps
- Set up the project
- Configure the pagination feature
- Using the OpenAPI spec
- Using the
liblab.config.json
file
- Generate the SDK
- Test the SDK
1. Set up the Project
First, log in to liblab using the following command:
liblab login
Now, you are ready to create the project. Create a new directory for your project called typescript-pagination and initialize the project by running the following commands:
mkdir -p typescript-pagination
cd typescript-pagination
liblab init
A new liblab.config.json
file will be created, containing all basic configurations for generating SDKs for four different programming languages:
- C#
- GO
- Python
- TypeScript
However, for this tutorial, we will only use TypeScript. Edit the liblab.config.json
file to only include TypeScript by modifying the languages
array:
"languages": [
"typescript"
],
2. Configure the pagination feature
liblab offers two ways to add pagination to your SDK:
- Using the OpenAPI spec.
- Directly via the
liblab.config.json
file.
Both methods achieve the same goal, so choose the one that best fits your workflow.
For more information on pagination settings, access the pagination documentation.
Below, you can choose one of the guides to follow with this tutorial to add the pagination feature to one endpoint of the Pokemon API.
- Pagination using OpenAPI spec
- Pagination using `liblab.config.json`
To configure pagination using the OpenAPI spec:
- Download the modified Pokémon API spec, which contains only the Get All Pokémon endpoint, and save it as
pokemon.yaml
in your project directory. Your directory structure should look like this:
typescript-pagination/
├── liblab.config.json
└── pokemon.yaml
- Update your
liblab.config.json
file to reference the Pokemon API you added to the directory:
"specFilePath": "./pokemon.yaml"
After completing these steps, you can configure pagination in the OpenAPI spec. In this example, you’ll add pagination to the GET /api/v2/pokemon/
endpoint, which lists all Pokémons available.
The code block below highlights the necessary changes to the OpenAPI spec.
paths:
/api/v2/pokemon/:
get:
operationId: pokemon_list
summary: List pokemon
x-liblab-pagination:
paginationType: offsetLimit
inputFields:
- name: offset
in: query
type: offset
- name: limit
in: query
type: limit
resultsArray:
results: "$.results"
tags:
- pokemon
parameters:
- name: limit
in: query
description: The maximum number of items to return in the response.
required: true
schema:
type: integer
format: int32
- name: offset
in: query
description: The number of items to skip before starting to collect the result set.
required: true
schema:
type: integer
format: int32
To add the pagination feature, you have to add the x-liblab-pagination
annotation to specify pagination settings. In the above code, the pagination type is set to offsetLimit
, which will be controlled by the offset
and limit
query parameters.
The inputFields
section defines the pagination inputs:
name
: Specifies the parameter's name (offset
orlimit
). It's important to note that the names used must match those used by the API server.type
: Indicates whether it’s anoffset
orlimit
.in
: Describes how the parameter is passed to the endpoint, in this case, as a query parameter. Currently, only thequery
option is available in liblab.
The resultsArray
object specifies the data location in the API response. The resultsArray.results
property should point to the array containing the results. If this is not correctly configured, errors may occur when using the SDK, as the SDK expects the response to return an array.
In this example, the API response uses the following schema:
{
"results": [],
"count": 123,
"next": "http://api.example.org/accounts/?offset=400&limit=100",
"previous": "http://api.example.org/accounts/?offset=200&limit=100"
}
Therefore, the resultsArray.results
property should be set to $.results
to ensure the SDK references the correct data for pagination.
The parameters
section, highlighted above, defines how the pagination parameters are handled:
- The
name
in the parameters must match the names in theinputFields
. - Both parameters (
limit
andoffset
) must be marked asrequired: true
. - Specify the
schema
, defining the parametertype: integer
andformat: int32
.
It's important to note that the names of the pagination parameters, such as offset
and limit
, must match those used by the API running on your server. Otherwise the SDK won't function properly.
After setting this up, you can proceed to generate the SDK.
To configure pagination using the liblab.config.json
file:
- Download the modified Pokémon API spec, which contains only the Get All Pokémon endpoint, and save it as
pokemon.yaml
in your project directory. Your directory structure should look like this:
typescript-pagination/
├── liblab.config.json
└── pokemon.yaml
- Update your
liblab.config.json
file to reference the Pokemon API you added to the directory:
"specFilePath": "./pokemon.yaml"
After completing these steps, you can configure pagination in the OpenAPI spec. In this example, you’ll add pagination to the GET /api/v2/pokemon/
endpoint, which lists all Pokémons available.
The code block below highlights the necessary changes to the liblab.config.json
file.
{
"customizations": {
"endpointCustomizations": {
"/api/v2/pokemon/": {
"get": {
"pagination": {
"paginationType": "limitOffset",
"inputFields": [
{
"name": "limit",
"in": "query",
"type": "limit"
},
{
"name": "offset",
"in": "query",
"type": "offset"
}
],
"resultsArray": {
"results": "$.results",
"defaultPageSize": 5
}
}
}
}
},
"includeOptionalSnippetParameters": true,
"devContainer": true,
"generateEnv": true,
"inferServiceNames": false,
"injectedModels": [],
"responseHeaders": false,
},
}
In the code above, the endpointCustomizations
annotation is used to specify pagination settings.
First, the endpoint and method to be customized are defined, which in this case are /api/v2/pokemon/
and get
. After, the pagination type is set to offsetLimit
, meaning pagination will be controlled by the offset
and limit
query parameters.
The inputFields
section defines the pagination inputs:
name
: Specifies the name of the parameter, in this exampleoffset
andlimit
. It's important to note that the names used must match those used by the API server.type
: Indicates whether it’s anoffset
orlimit
parameter.in
: Describes how the parameter is passed to the endpoint, in this case, as a query parameter. Currently, only thequery
option is available in liblab.
When using the pagination feature with your API, ensure that all parameter names match those accepted by the API running on your server. Otherwise, the SDK may not function correctly.
The resultsArray
object specifies the data location in the API response. The resultsArray.results
property should point to the array containing the results. If this is not correctly configured, errors may occur when using the SDK, as the SDK expects the response to return an array.
In this example, the API response uses the following schema:
{
"results": [],
"count": 123,
"next": "http://api.example.org/accounts/?offset=400&limit=100",
"previous": "http://api.example.org/accounts/?offset=200&limit=100"
}
Therefore, the resultsArray.results
property should be set to $.results
to ensure the SDK references the correct data for pagination.
When setting the pagination through the liblab.config.json file, you also have to define the defaultPageSize
, which is used as the default value for limit
in case it isn't provided in the request.
After setting this up, you can proceed to generate the SDK.
3. Generate the SDK
Once you've configured pagination, generate the SDK by running:
liblab build
The liblab CLI will validate and notify you about any issues with the liblab.config.json
or the pokemon.json
files.
If you receive the message:
It is important to fix your spec before continuing with a build. Not fixing the spec may yield a subpar SDK and documentation. Would you like to attempt to build the SDK anyway? (Y/n)
Type Y
to continue.
liblab CLI checks if errors exist in the OpenAPI spec and warns you because the existing errors may affect the SDK behavior. If you facing this warning while following this tutorial, you can type Y
since you'll be using a tested spec.
After a successful build, you should see the following message on your terminal:
Your SDKs are being generated. Visit the liblab portal (<link to the liblab page>) to view more details on your build(s).
✓ TypeScript built
Successfully generated SDKs downloaded. You can find them inside the <your-repository> folder
✓ Generate package-lock.json for TypeScript
Successfully generated SDK's for TypeScript ♡
The SDK will be generated and saved in the /output/typescript
directory, which should have the following structure:
/output/typescript
├── documentation/
├── examples/
├── LICENSE
├── node_modules/
├── package.json
├── package-lock.json
├── README.md
├── src/
└── tsconfig.json
The documentation available lists and describes the SDK models and services. Only the pokemonList method will be available for the modified Pokemon API. If you access the service documentation, you will find the details on how to use it:
## pokemonList
- HTTP Method: `GET`
- Endpoint: `/api/v2/pokemon/`
**Parameters**
| Name | Type | Required | Description |
| :----- | :----- | :------- | :----------------------------------- |
| limit | number | ✅ | How many items to return at one time |
| offset | number | ✅ | Starting point of data |
4. Test the SDK
To test the SDK, you can use the automatically generated sample code located at the example directory:
- Navigate to the
/output/typescript/examples/
directory:
cd output/typescript/examples/
- Run the following command to set up the TypeScript SDK:
npm run setup
The TypeScript SDK is not published to npm. You will be using the local files to run it.
Access the liblab documentation to learn how to publish your SDK.
Once setup is complete, you're ready to test the SDK pagination feature. The following examples show how to test both manual and iterable pagination. Both manual and iterable pagination offer flexibility:
- Manual pagination gives you control over when to fetch the next page. This is useful for scenarios where you don’t need all data upfront and prefer fetching data on demand.
- Iterable pagination automatically handles loading all pages as you iterate through them. This is ideal when you need to process all available data sequentially.
Now, you can test both approaches by using using the index.ts
file located at output/typescript/examples/src/index.ts
and the code examples below.
- Manual pagination
- Iterable pagination
With manual pagination, you can use the next()
function to fetch subsequent data pages based on the defined limit
and offset
values. Replace the content in output/typescript/examples/src/index.ts
with the code below to test a simple implementation of manual pagination for the pokemonList
method.
import { TypeScriptSdk } from 'typescript-sdk';
(async () => {
const Sdk = new TypeScriptSdk({
token: 'YOUR_TOKEN',
});
const pages = await Sdk.pokemon.pokemonList({
limit: 8,
offset: 0,
});
const firstPage = await pages.next();
console.log(firstPage.value.data);
const secondPage = await pages.next();
console.log(secondPage.value.data);
})();
In the above code:
- The
TypeScriptSdk
is imported from the SDK packagetypescript-sdk
, and a new instance is created. - The
pokemonList
function from the SDK retrieves a list of Pokémon. Thepages
variable is an iterable object. Each page will include up to 8 Pokémon, as defined by thelimit
value. Theoffset
specifies where the first page starts in the list, withoffset: 0
meaning the data begins at the first Pokémon. By iterating throughpages
, you can access each page of data in sequence, with each page containing the number of entries defined bylimit
. - When the
next()
method is called for the first time, it retrieves the first page of results. - After, the
next()
method is called again to fetch the second page of results.
Run the following commant at the output/typescript/examples/
directory to test the code above:
npm run start
The following tabs display the Pokémon data retrieved on each page. Each page contains 8 Pokémon, and the first page starts with the first Pokémon.
- First page
- Seccond page
[
{ name: 'bulbasaur', url: 'https://pokeapi.co/api/v2/pokemon/1/' },
{ name: 'ivysaur', url: 'https://pokeapi.co/api/v2/pokemon/2/' },
{ name: 'venusaur', url: 'https://pokeapi.co/api/v2/pokemon/3/' },
{ name: 'charmander', url: 'https://pokeapi.co/api/v2/pokemon/4/' },
{ name: 'charmeleon', url: 'https://pokeapi.co/api/v2/pokemon/5/' },
{ name: 'charizard', url: 'https://pokeapi.co/api/v2/pokemon/6/' },
{ name: 'squirtle', url: 'https://pokeapi.co/api/v2/pokemon/7/' },
{ name: 'wartortle', url: 'https://pokeapi.co/api/v2/pokemon/8/' }
]
[
{ name: 'blastoise', url: 'https://pokeapi.co/api/v2/pokemon/9/' },
{ name: 'caterpie', url: 'https://pokeapi.co/api/v2/pokemon/10/' },
{ name: 'metapod', url: 'https://pokeapi.co/api/v2/pokemon/11/' },
{ name: 'butterfree', url: 'https://pokeapi.co/api/v2/pokemon/12/' },
{ name: 'weedle', url: 'https://pokeapi.co/api/v2/pokemon/13/' },
{ name: 'kakuna', url: 'https://pokeapi.co/api/v2/pokemon/14/' },
{ name: 'beedrill', url: 'https://pokeapi.co/api/v2/pokemon/15/' },
{ name: 'pidgey', url: 'https://pokeapi.co/api/v2/pokemon/16/' }
]
Applying manual pagination using the next()
function allows you to retrieve one page at a time, allowing precise control over data loading.
When you set the pagination feature with liblab's SDKs and perform a request to the endpoint, it returns an iterable object, allowing you to use it in a for
loop. By setting the offset
and limit
, you can automatically generate customizable data pages. Replace the content in output/typescript/examples/src/index.ts
with the code below to test a simple implementation of iterable pagination for the pokemonList
method.
import { TypeScriptSdk } from 'typescript-sdk';
(async () => {
const typeScriptSdk = new TypeScriptSdk({
token: 'YOUR_TOKEN',
});
const pages = await typeScriptSdk.pokemon.pokemonList({
limit: 5,
offset: 3,
});
for await (const page of pages) {
console.log(page.data);
}
})();
In the above code:
- The
TypeScriptSdk
is imported from the SDK packagetypescript-sdk
, and a new instance is created. - The
pokemonList
function from the SDK retrieves a list of Pokémon. Thepages
variable is an iterable object. Each page will include up to 5 Pokémon, as defined by thelimit
value. Theoffset
specifies where the first page starts in the list, starting from the fourth Pokémon sinceoffset: 3
. By iterating throughpages
, you can access each data page in sequence, with each page containing the number of entries defined bylimit
. - The
for await...of
loop asynchronously iterates through the pages object, representing pages of data generated by the SDK.
Run the following command at the output/typescript/examples/
directory to test the code above:
npm run start
The following tabs display the first, second, and last page results. Note that the first page starts with the fourth Pokémon, defined by offset: 3
, and the last page contains fewer than 5 entries.
- First page
- Seccond page
- Last page
[
{ name: 'charmander', url: 'https://pokeapi.co/api/v2/pokemon/4/' },
{ name: 'charmeleon', url: 'https://pokeapi.co/api/v2/pokemon/5/' },
{ name: 'charizard', url: 'https://pokeapi.co/api/v2/pokemon/6/' },
{ name: 'squirtle', url: 'https://pokeapi.co/api/v2/pokemon/7/' },
{ name: 'wartortle', url: 'https://pokeapi.co/api/v2/pokemon/8/' }
]
[
{ name: 'blastoise', url: 'https://pokeapi.co/api/v2/pokemon/9/' },
{ name: 'caterpie', url: 'https://pokeapi.co/api/v2/pokemon/10/' },
{ name: 'metapod', url: 'https://pokeapi.co/api/v2/pokemon/11/' },
{ name: 'butterfree', url: 'https://pokeapi.co/api/v2/pokemon/12/' },
{ name: 'weedle', url: 'https://pokeapi.co/api/v2/pokemon/13/' }
]
[
{ name: 'ogerpon-hearthflame-mask', url: 'https://pokeapi.co/api/v2/pokemon/10274/' },
{ name: 'ogerpon-cornerstone-mask', url: 'https://pokeapi.co/api/v2/pokemon/10275/' },
{ name: 'terapagos-terastal', url: 'https://pokeapi.co/api/v2/pokemon/10276/' },
{ name: 'terapagos-stellar', url: 'https://pokeapi.co/api/v2/pokemon/10277/' }
]
Conclusion
You can easily manage large datasets using manual or iterable pagination methods by configuring pagination through either the OpenAPI spec or the liblab.config.json
file. Whether you need precise control over data fetching or seamless iteration through all results, liblab’s SDK simplifies the process, improving developer experience.