Building a Choose-Your-Own-Adventure API with NestJS — Part 1: The Foundation
This is the first post in a series where I learn backend development by building "Grimoire API" — a REST API for a choose-your-own-adventure book, complete with XP and badges (because why not gamify learning to code?). If you know a bit of JavaScript and have played with Express, but NestJS still looks like a wall of decorators, this post is for you. Why a text adventure API? I wanted a project I'd actually stay motivated to finish. "Build a generic CRUD API" is the kind of tutorial I abandon after day two. So instead, I'm building the backend for a livre dont vous êtes le héros — a choose-your-own-adventure book — where players read a page, pick a choice, and move forward through the story while earning XP and unlocking badges. The learning goal comes first, the game is the excuse. Every milestone in this project maps to a specific NestJS concept I want to actually understand, not just copy-paste. This post covers Milestone 1: The Foundation — the very first working endpoint: GET /pages/:id , which returns one page of the story as JSON. Where I was starting from I'd written basic Express routes before — app.get('/something', (req, res) => {...}) , that kind of thing. Simple, direct, no magic. NestJS, on the other hand, throws a lot of new vocabulary at you on day one: modules, controllers, providers, dependency injection, decorators, DTOs. It's easy to feel like you need to understand the whole framework before writing a single line. Turns out you don't. Here's the minimum you need to build one real endpoint. The four building blocks NestJS organizes code around four ideas. Once they click, everything else is a variation on them. 1. Controllers — the front door A controller's only job is to receive an HTTP request and return a response. It should not contain business logic. Mine looks like this: @ Controller ( ' pages ' ) export class PagesController { constructor ( private readonly pagesService : PagesService ) {} @ Get ( ' :id ' ) getPage (@ Param () params : Get