Building a Choose-Your-Own-Adventure API with NestJS — Part 2: Persistence
Part 2 of a series building "Grimoire API" — a choose-your-own-adventure backend in NestJS, learned one milestone at a time. Part 1 covered controllers, providers, DI and validation with a single read-only endpoint. This time: making a player's progress survive a server restart. The problem with Part 1 By the end of Part 1, GET /pages/:id worked — but every request started from scratch. There was…
In the second part of building the Choose-Your-Own-Adventure API with NestJS, the focus is on persistence to ensure a player's progress remains intact even if the server restarts. Part 1 covered controllers, providers, dependency injection, and validation with a single read-only endpoint. This section introduces making a player's progress survive a server restart by leveraging a database.
The issue with Part 1 was that every request started from scratch, with no concept of a player or where they were in the story. A database is necessary to remember the player's progress between requests. For this project, PostgreSQL and TypeORM are chosen as the database and ORM pairing, respectively. TypeORM is a common choice with NestJS, well-documented, and closely integrated with NestJS's dependency injection.
TypeORM entities map to database tables. The `PlayerProgress` entity represents a player's progress in the story, with properties for the player's ID, current page ID, and XP. The relationship to the `User` entity is established using `@OneToOne` and `@JoinColumn`. The decision not to include a `level` column is based on the premise that level is calculated from XP and should never be stored as an independent value.
This approach eliminates the possibility of inconsistencies between XP and level, ensuring the API cannot lie to the player about their progress.
To integrate TypeORM with the module, `TypeOrmModule.forFeature([PlayerProgress])` is used. This makes the `PlayerProgress` repository available for injection inside the module. The repository pattern is then utilized in the `ProgressService`, where raw SQL is avoided, and repository methods such as `findOne`, `save`, and `update` are employed. The `findForUser` method retrieves a player's progress, while the `advance` method updates the progress by advancing the player to the next page.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.