nestjs-typeorm-paginate: a fixed response envelope for NestJS lists
:page_with_curl: Pagination response object function + types for typeorm + nestjs
At a glance
- What is it?
- This small TypeScript package wraps a TypeORM repository or query builder in a paginate function that returns a strict shape: items, meta and links. It is aimed at NestJS teams that want the same JSON contract on every list endpoint, and it is deliberately narrow about what it does.
- Who is it for?
- Adopt nestjs-typeorm-paginate when your NestJS service exposes many list endpoints and you want one pagination contract, one set of count semantics and typed options instead of hand-written skip and limit code in every controller. Do not adopt it if your API needs a different envelope, a cursor based scheme, or a response without a total count, because the items, meta and links structure is the package rather than a detail of it.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One function wrapping every list endpoint in the same envelope
The package is described in the repository as a "Pagination helper method for TypeORM repositories or queryBuilders with strict typings", published on npm as nestjs-typeorm-paginate and written in TypeScript. The topics list is short: nestjs, pagination, typeorm.
The problem it addresses is not pagination mathematics. It is the fact that every list endpoint in a NestJS application tends to grow its own hand-rolled response object, its own count query and its own link construction, and that the shapes drift apart. This package gives you a single paginate function that accepts a TypeORM repository or a query builder plus a set of typed options, and returns a Promise of a generic Pagination type parameterised by your entity.
The audience is therefore a backend developer with an existing NestJS and TypeORM application who wants a consistent JSON contract across many endpoints. The type parameters matter as much as the runtime behaviour here: the package description calls out strict typings, and the generic form in the README, Pagination<CatEntity>, is what carries the entity type through to the controller layer.
It is a helper library, not a framework component. There is no module to register, no interceptor, no guard, and no configuration file.
The response always has three parts, and the links depend on route
Every call returns the same envelope with three top level keys: items, meta and links. The README documents each one, and the distinctions matter when you integrate with a front end.
Inside meta, itemCount is the length of the items array, so the number of records actually on this page. totalItems is the total number of entities matching the filter conditions, not the number returned. itemsPerPage echoes the requested limit, totalPages is computed from that limit, and currentPage is the page the paginator points at.
The links object is where the optional parts of the contract show. first and last are empty strings when no route is defined, previous is empty when there is no previous page to call, and next is empty when there is no next page. The README also flags a subtlety: the first link may not carry a page query parameter at all, so a client that assumes page is always present will need to handle the first URL on its own.
Because these fields are fixed, the response cannot be slimmed down. If your API contract wants only items and a total, this envelope gives you more than you asked for, and the empty-string placeholders in links are the visible symptom of that rigidity.
Installing and wiring the repository variant
The install step is a single dependency, and the README offers both package managers. The yarn form is:
$ yarn add nestjs-typeorm-paginateand the npm form is:
$ npm i nestjs-typeorm-paginateThe first real use is a service method. The README's repository example injects the entity repository with @InjectRepository, then calls paginate with the entity type parameter:
@Injectable()
export class CatService {
constructor(
@InjectRepository(CatEntity)
private readonly repository: Repository<CatEntity>,
) {}
async paginate(options: IPaginationOptions): Promise<Pagination<CatEntity>> {
return paginate<CatEntity>(this.repository, options);
}
}Three names come out of the package here: the paginate function itself, the Pagination type returned to the caller, and IPaginationOptions describing the accepted options. Because the service returns the typed Pagination<CatEntity> and the controller awaits it, the entity type survives the hop from the data layer to the HTTP layer without a cast.
The same call works with a query builder. The README shows the query builder variant creating a builder with createQueryBuilder, applying an order, and passing that builder to the same paginate function, so ordering and filtering stay in the query layer while pagination is applied by the package.
The route option decides whether links are useful
The links in the response are built from a route option, and the README explains the two acceptable forms. It can be an absolute URL, as in http://cats.com/cats in the controller example, or the short version of an absolute path, in which case it would be /cats instead of a full URL. That choice is a real fork in behaviour. With a full URL, the generated links are absolute and can be handed straight to a client that follows them. With a short path, the links are relative.
The controller example shows how the options are assembled from query parameters:
@Get('')
async index(
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number = 1,
@Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number = 10,
): Promise<Pagination<CatEntity>> {
limit = limit > 100 ? 100 : limit;
return this.catService.paginate({
page,
limit,
route: 'http://cats.com/cats',
});
}Two details in that snippet are worth copying carefully. The limit is clamped to 100 before it reaches the package, which is a cap the package itself does not impose. And the README warns that if you use ParseIntPipe on the query parameters, as the example does, you should also add DefaultValuePipe, pointing at issue 517 for more information. Without the default pipe, a request with no page or limit parameter reaches the pipe as an empty value rather than a number, and the failure appears at the edge of your application rather than inside the pagination code.
Find parameters and the eager loading caveat
Filtering is supported through a third argument to paginate, described in the README as find parameters. The example passes a plain object of column values:
return paginate<CatEntity>(this.repository, options, {
lives: 9,
});The object is passed to TypeORM as the criteria for the query, so the totalItems count in the meta block reflects the same filter, not the unfiltered table. That is the behaviour to verify first, because a mismatch between the counted rows and the returned rows is the most common source of a broken pagination UI.
Eager loading is handled by TypeORM's own eager property, and the README states that it should work out of the box:
@Entity()
export class CatEntity {
@OneToMany(t => TigerKingEntity, tigerKing.cats, {
eager: true,
})
tigerKings: TigerKingEntity[];
}Two caveats come with that snippet. As printed, the relation callback references tigerKing without defining it, so the example needs adjusting before it compiles in your own code. And the README section on the query builder is cut off mid sentence, stating that when using the query builder there is a difference, without finishing the explanation. Eager relations through the query builder path are therefore the part of this package you should test yourself before relying on it.
Peer ranges, no tagged releases, and a mysql fixture for the tests
The package.json is where the compatibility facts live, and they do not fully match the README. The peer dependencies allow @nestjs/common from ^5.6.2 up through ^11.0.0, with older ranges for the earlier majors, and typeorm at ^0.3.0. The README, meanwhile, still carries the note that users on typeorm^0.2.6 should use nestjs-typeorm-paginate^3.2.0, and that typeorm^0.3.0 users should use nestjs-typeorm-paginate^4.0.0.
That gap between the declared peer range and the version mapping in the README is the first thing to check at install time, because the resolution rules of your package manager decide whether you land on the version your TypeORM expects.
There is one more packaging fact: the repository has no GitHub releases. Version tracking therefore happens on npm, and the tags you rely on are npm versions rather than GitHub release entries. The build is plain TypeScript. The scripts run prettier for formatting, compile with tsc, and rebuild the index files with a separate declaration step, so the published entry point is generated rather than hand written.
For development, the repository carries a docker-compose.yml with a mysql 5.7 service on port 3306, an ormconfig.json, a .env.dist file, and a setup:test script that runs typeorm schema:sync. Tests run through jest in band mode, and coverage is reported to coveralls, with a coveralls.json at the root. The licence is MIT.
On maintenance, the last push was on 2026-10-19, so the code is being touched, but the absence of tagged releases means there is no changelog-driven upgrade path to follow.
When writing the count query by hand is the better call
The package is the wrong tool in a few identifiable situations, and they all come from the fixed envelope rather than from the pagination logic.
If your API needs a cursor based scheme, where clients continue from the last seen identifier instead of requesting page numbers, the items, meta and links shape does not map onto it, since the meta block is built around a total count and a page index. The same applies to an endpoint where exposing the total number of matching rows is not acceptable, since totalItems is not optional in the response. And for a one-off internal endpoint that returns a handful of records, adding a dependency to produce a links object nobody consumes is more machinery than the problem needs.
The alternative is to skip the package and build the response in your own service, using TypeORM for the query and your own count query and URL construction. The difference in approach is direct: this package owns the count, the arithmetic and the link generation in one typed call, while a hand-rolled version spreads that logic across your service and controller, where each endpoint can drift slightly. Choose the package when you have many list endpoints and want one contract, and choose the hand-rolled version when the contract has to be something other than the one the package defines.
Editorial conclusion
Adopt nestjs-typeorm-paginate when your NestJS service exposes many list endpoints and you want one pagination contract, one set of count semantics and typed options instead of hand-written skip and limit code in every controller. Do not adopt it if your API needs a different envelope, a cursor based scheme, or a response without a total count, because the items, meta and links structure is the package rather than a detail of it. Before you install, check the peer dependency range for typeorm in package.json, since the package declares typeorm ^0.3.0 while the README still carries guidance for typeorm^0.2.6 users on nestjs-typeorm-paginate^3.2.0, and confirm that the limit clamp of 100 in the controller example matches your own page size policy.
Frequently asked questions
How do I install nestjs-typeorm-paginate?
The README gives two forms, yarn add nestjs-typeorm-paginate and npm i nestjs-typeorm-paginate. It also notes that typeorm^0.2.6 users should use nestjs-typeorm-paginate^3.2.0, and typeorm^0.3.0 users should use nestjs-typeorm-paginate^4.0.0.
Which TypeORM and NestJS versions does nestjs-typeorm-paginate support?
The package.json declares peer dependencies of @nestjs/common from ^5.6.2 through ^11.0.0, and typeorm at ^0.3.0. The README also carries version guidance for typeorm^0.2.6 users, which predates the declared peer range.
How do I get pagination links in the response?
Pass a route option to paginate, as the controller example does with route: 'http://cats.com/cats'. The README says the short version of an absolute path also works, in which case it would be /cats. Without a route, the first and last links are empty strings.
Can nestjs-typeorm-paginate paginate a query builder as well as a repository?
Yes. The README shows a service that creates a query builder with createQueryBuilder, applies an order with orderBy, and passes that builder to the same paginate function used for repositories. The README section on eager loading with the query builder is cut off, so that combination is worth testing yourself.
Does nestjs-typeorm-paginate have GitHub releases?
No, the repository has no GitHub releases, so versions are tracked on npm. The last push to the repository was on 2026-10-19 and the licence is MIT.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/nestjsx-nestjs-typeorm-paginate)Was this useful?