To turn stored story progress into game mechanics in NestJS, keep three jobs separate: persist the player’s progress, calculate level from total XP, and award badges only when the player does not already have them. In this installment of the Grimoire API series, the choice’s existing xpReward and optional badgeUnlocked fields begin driving those behaviors.
That division keeps ProgressService focused on progress and gives each game rule a clear place to change and test. The level formula and badge flow below describe the tutorial’s implementation, not universal game-design standards.
As an Amazon Associate I earn from qualifying purchases.
What changes when story choices become game mechanics?
Part 1 of the Grimoire API series established a validated, read-only endpoint; Part 2 added player progress persistence. Part 3 connects the reward metadata already attached to story choices to player state. Choosing an available option now adds its XP reward, updates the current page, saves progress, and checks the destination page for a badge award.
Rather than adding XP calculations and badge-specific conditionals to ProgressService, the tutorial separates the rules into dedicated services. That follows NestJS’s provider model: controllers handle HTTP requests and delegate application logic to providers, which are wired through dependency injection. See the NestJS controllers documentation and providers documentation.
#1 Best Overall
How does the tutorial calculate a level from XP?
The tutorial’s XpService derives a player’s level from total XP using a square-threshold formula: the threshold for level N is N² × 100 XP. Under that project-specific rule, level 2 starts at 400 XP and level 3 starts at 900 XP. The tutorial’s level-1 example places the player below 400 XP.
The service also provides an xpForNextLevel value for the next threshold. Since level can be derived from total XP, there is no need to persist a separate level value that could drift out of sync with XP. This formula is the tutorial author’s chosen progression curve; it is not an industry standard.
Why keep XP calculation independent?
XP-to-level conversion is a calculation, not a database operation. Keeping it pure means it can be tested against boundary values without setting up persistence: for example, verify the result immediately below and at each threshold. It also avoids coupling the progression curve to the code that saves player records.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesHow does the API prevent duplicate badge awards?
The tutorial’s BadgesService checks for an existing player-badge record before creating one. If the player already has the badge, the service returns without issuing it again. The progress flow invokes this check for a badge associated with the destination page after saving the updated progress.
Rank #3
That read-then-create check handles repeated calls in the illustrated sequential flow, but it is not by itself safe against concurrent requests: two requests can both observe no record and then attempt to insert one. A production database should enforce uniqueness for the player-and-badge pair, or use an equivalent concurrency-safe mechanism. The tutorial does not demonstrate such a constraint.
What happens when a player advances?
- Read and validate the requested choice identifier at the HTTP boundary.
- Confirm that the requested choice is actually available on the player’s current page; a valid identifier alone does not make a choice available in that state.
- Add the selected option’s
xpRewardto the player’s total XP and update the current page to the option’s destination. - Save the updated progress.
- Check the destination page’s optional
badgeUnlockedvalue and award the badge if the player does not already hold it.
The order matters: the badge rule in this flow belongs to the destination page reached by the choice, and it runs after the progress update is saved. The article also introduces an InvalidChoiceException derived from NestJS’s BadRequestException. That gives the domain failure a more precise name while retaining a 400-class HTTP response.
Rank #4
Where should choice validation happen?
Validate and normalize caller-supplied choice identifiers at the request boundary, then let the game logic check whether that choice belongs to the player’s current page. NestJS documents both ValidationPipe for decorated DTO classes and StandardSchemaValidationPipe for compatible validation schemas, as well as parsing pipes for individual values. See the NestJS validation documentation.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteThese checks answer different questions: boundary validation establishes that the input has the expected shape, while the game service enforces the state-dependent rule that the player may take only an available choice. A custom invalid-choice exception makes that latter failure distinguishable in the application code.
Best Value
What belongs in the next installment, not this game-rule layer?
The series uses a temporary default player in this stage; its next installment moves progress to authenticated accounts. Authentication and authorization therefore remain a separate concern from calculating XP or deciding whether a badge is already owned. NestJS guards are designed for request authorization checks using execution context, and run after middleware but before interceptors or pipes. See the NestJS guards documentation.
The tutorial uses PostgreSQL with TypeORM for persistence, but neither is required by NestJS or by these game rules. NestJS documents integrations for TypeORM, Drizzle, Mongoose, Prisma, MikroORM, and Sequelize. See the NestJS database documentation. The XP calculation can stay independent of whichever persistence integration stores player progress and badge records.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.

