trekker
A hiking API where every request first goes through the gauge of the shared contract.
- Stack
- TypeScript · NestJS · Drizzle
- Year
- 2026
- Repository
- github.com/Popoch39/trekker ↗
Anatomy
8 parts · only one countsTake the request
Takes in the list request; with no open session, the API answers 401.
TreksController15
@ApiTags('treks')16
@Controller('treks')17
export class TreksController {Check every parameter
One contract for the API, web and mobile: filters, position, 100 per page.
trekListQuerySchema133
export const trekListQuerySchema = paginationQuerySchema134
.extend({135
difficulty: trekDifficultySchema.optional(),Turn down a bad request
Answers 422 with the list of faulty fields, in the standard RFC 9457 format.
ZodValidationException52
if (exception instanceof ZodValidationException) {53
return {54
...base,Cut out a page
Sorts by name then id, skips the earlier pages and keeps 20 routes.
toLimitOffset()127
const where = conditions.length > 0 ? and(...conditions) : undefined;128
const { limit, offset } = toLimitOffset(query);130
const [rows, total] = await Promise.all([Count the results
Counts every result at the same time: 100 routes make 5 pages.
$count()154
this.db.$count(treks, where),155
]);157
return {Slim down each result
Serves name, distance and climb without the GPS track, too heavy for a list.
trekSummarySchema55
export const trekSummarySchema = entityBaseSchema.extend({56
name: nonEmptyString,57
distanceMeters: z.int().min(0),Speed up the sort
An index on name then id: 0.9 ms instead of 130 ms per page, across 2,442 routes.
treks_name_id_idx148
index('treks_name_id_idx').on(table.name, table.id),Store the routes
A Postgres table: one row per route, with its track and its starting point.
treks57
export const treks = pgTable(58
'treks',59
{
Pull a handle to request its pageDrag a drawer towards you to request its page
Parts list
- 01 Lock slot Takes in the request card; the lock demands a session. Code: apps/api/src/modules/treks/treks.controller.ts, lines 8–24 ↗
- 02 Pin gauge Checks every field of the request card, and its interlocks, before letting it through. Code: packages/contracts/src/treks.ts, lines 126–185 ↗
- 03 Reject tray Catches the refused card, with the details of the faulty fields. Code: apps/api/src/common/filters/problem-details.filter.ts, lines 52–61 ↗
- 04 Rod and follower Holds the cards in name, id order; the follower sets twenty apart, after those of the earlier drawers. Code: apps/api/src/modules/treks/treks.service.ts, lines 127–151 ↗
- 05 Totaliser Counts every matching card while the drawer slides out, and works out the number of pages. Code: apps/api/src/modules/treks/treks.service.ts, lines 152–160 ↗
- 06 Summary card What comes out of the drawer: the card without its track, which stays at the bottom. Code: packages/contracts/src/treks.ts, lines 49–74 ↗
- 07 Ball-bearing runners Bring the drawer out without sorting the whole cabinet. Code: packages/db/src/schema/treks.ts, lines 142–148 ↗
- 08 Cabinet body The cabinet itself: each card is filed in it once. Code: packages/db/src/schema/treks.ts, lines 57–91 ↗