Warming up the neural circuits...
By the end of this chapter you will:
You can't navigate a codebase you don't understand. Before writing anything, know what each dependency is and why it exists.
When you open a file in this codebase, you will see imports like:
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { IsString, IsUUID } from 'class-validator';If you don't know what each of those libraries is, you'll be lost. This chapter gives you a mental map — read it once, then come back when something confuses you.
that runs on your server, not in a browser. Node.js takes the same language and lets it run as a server process — reading files, listening on ports, connecting to databases.
You interact with it: Rarely directly. You run node dist/main to start the app.
JavaScript with types added on top. Normal JavaScript has no types — you can write const x = "hello" and do x + 5 and it won't complain until it breaks at runtime. catches this at compile time.
// TypeScript knows this is wrong at compile time:
function add(a: number, b: number): number {
return a + b;
}
add("hello", 5); // ❌ Error: Argument of type 'string' is not assignable to 'number'You interact with it: All .ts files. Run npm run build to compile to JavaScript.
The main framework — the skeleton of the entire app. NestJS is built on top of Express and adds structure using decorators — those @Something() annotations you see everywhere.
@Controller('orders') // This class handles routes that start with /orders
export class OrdersController {
@Get(':id') // This method handles GET /orders/:id
findOne(@Param('id') id: string) {
return this
You interact with it: Every controller, service, module, guard, and pipe you write.
The underlying HTTP server NestJS runs on top of. Express handles raw networking: listening on a port, parsing HTTP headers, routing requests. NestJS wraps it so you never deal with it directly.
You interact with it: Rarely. Sometimes in custom .
= Object-Relational Mapper. Instead of writing raw , you write TypeScript and the ORM translates it.
Without ORM:
SELECT * FROM orders WHERE user_id = 'usr-123' AND status = 'pending';With ORM (Sequelize):
await this.orderModel.findAll({
where: {
The primary used in this project. Works with PostgreSQL. Sequelize maps your TypeScript classes (called "models") to database tables.
You interact with it: Every time you query the database — findAll, findByPk, create, update, destroy.
A TypeScript wrapper that adds decorator syntax to Sequelize:
@Table({ tableName: 'orders' })
export class Order extends Model<Order> {
@PrimaryKey
@Default(DataType.UUIDV4)
@Column(DataType.UUID)
id
You interact with it: Every model file (*.entity.ts) uses these decorators.
An ODM (Object Document Mapper) for MongoDB — a completely different kind of database.
Sequelize talks to PostgreSQL (relational, tables, rows). Mongoose talks to MongoDB (document store, JSON-like objects). They are completely separate tools for completely different databases.
You interact with it: Only if you are working with MongoDB-specific features.
The main relational database. All the core QuickBite data lives here: users, restaurants, menu items, orders, order items, and payments. ACID-compliant — meaning if multiple things happen at once (e.g. a user places an order while the restaurant updates its menu), they are handled correctly without corrupting data.
You interact with it: Through Sequelize. You rarely write raw SQL unless optimizing a complex query.
A small, self-contained "ticket" that proves who you are. When you log in, the server gives you a JWT. On every future request, you attach that token, and the server can verify you — without looking you up in the database every time.
A JWT looks like: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyLTEyMyJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Three parts separated by dots:
The payload is readable by anyone. Never put passwords or sensitive data in the JWT.
A library for hashing passwords. Passwords must never be stored as plain text. bcrypt turns hunter2 into $2b$12$sLLTgC... — a one-way hash that cannot be reversed. It's intentionally slow, which makes brute-forcing impractical.
Express middleware that sets secure HTTP headers automatically with one line:
X-Frame-Options: DENY — prevents clickjackingX-Content-Type-Options: nosniff — prevents MIME sniffing attacksStrict-Transport-Security — forces HTTPSAn in-memory database. Extremely fast (microseconds vs milliseconds for PostgreSQL). Used for:
Redis data is stored in memory. If the server restarts without persistence configured, data is lost. Never store data here that absolutely cannot be lost.
A message broker — a middleman that delivers messages between services. amqplib and amqp-connection-manager are the Node.js packages that connect to RabbitMQ.
Instead of the waiter running to the kitchen and waiting for the food, they leave a ticket in a . The kitchen processes tickets when ready. This is what RabbitMQ does for software — the leaves a "job ticket" and the worker picks it up when ready.
Amazon's file storage. When a restaurant owner uploads a menu photo, it doesn't go into the database (databases are terrible at storing large files). It goes to S3. The database stores only the S3 file URL. Same for receipt PDFs and profile pictures.
A tool that packages your app and all its dependencies into a portable "container." With , you package exactly the Node version, dependencies, and config into one image. CI, staging, and production all run that exact same image.
You interact with it: docker-compose up to start local development.
A system that runs and manages Docker containers at scale in production. Kubernetes runs 50 containers, restarts them when they crash, distributes traffic, and scales up/down based on load. This is why the app has /healthz and /readyz endpoints — uses them to know whether to route traffic to a pod.
A library that validates incoming request data using decorators:
export class CreateOrderDto {
@IsUUID()
restaurantId: string; // if not a valid UUID, request is rejected with 400
@IsArray()
@ArrayMinSize(1)
items: OrderItemDto[]; // if empty array, request is rejected with 400
@IsString()
@IsOptional
You interact with it: Every DTO (Data Transfer Object) file uses these decorators.
Automatically generated interactive API documentation. When you add @ApiProperty() to your DTOs and @ApiTags() to your controllers, NestJS generates a documentation page at /v1/api-docs. Frontend developers and QA can test your API directly from the browser.
If the Swagger docs are wrong, partners integrate incorrectly. Treat the Swagger page as your API contract, not a nice-to-have.
The test runner. It finds all *.spec.ts files, runs them, and reports results.
npm test # run all tests
npm run test:cov # run with coverage reportA library for testing HTTP endpoints without starting a real server. Sends real HTTP requests to your NestJS app in tests and lets you assert on the response.
| Library | Type | Purpose |
|---|---|---|
@nestjs/common | Framework | Core decorators: @Controller, @Injectable, etc. |
sequelize | ORM | Query the database |
sequelize-typescript | ORM addon | Decorator-based model definitions |
class-validator | Validation | Validate request bodies |
class-transformer | Transform | Convert JSON to typed instances |
@nestjs/swagger | Docs | Auto-generate API docs |
@nestjs/jwt | Auth | Issue and verify JWTs |
bcryptjs | Security | Hash passwords |
helmet | Security | Set secure HTTP headers |
redis | /Queue | Fast in-memory storage |
|
Every library exists to solve one specific problem. When you encounter an unfamiliar import, ask: "what problem is this solving?" The answer is almost always one of: validation, authentication, database access, or observability.
The second version is typed, autocompleted, and less error-prone.
| DB driver |
| Connect to PostgreSQL |
mongoose | ODM | Connect to MongoDB |
jest | Testing | Run tests |
winston | Logging | Structured log output |