Warming up the neural circuits...
By the end of this chapter you will:
Most backend projects fail in their first month — not because the code is bad, but because the setup was rushed.
You've seen it before: a repo with a vague npm install && npm start in the README. The database URL is hardcoded in a file called config.js. There's no way to tell if the app actually started correctly. And the first time you try to run it, you spend 45 minutes figuring out what's missing.
This chapter gives you a day-1 checklist — a repeatable process you follow for every new service you create. Do it once, do it right, and never waste time debugging startup again.
Every new NestJS project needs these 7 things before you write a single business feature:
nest new/healthz and /readyz.gitignore and README.md — the basicsLet's go through each one.
nest newnpm install -g @nestjs/cli
nest new quickbite-apiChoose npm as the package manager. NestJS generates this structure:
quickbite-api/
├── src/
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test/
│ └── app.e2e-spec.ts
├── .eslintrc.js
├── .prettierrc
├── nest-cli.json
├── package.json
├── tsconfig.build.json
└── tsconfig.jsonDelete the generated app.controller.ts, app.service.ts, and app.controller.spec.ts — they're just a hello world demo. You'll build your own controllers soon.
Here's the folder layout you'll use for QuickBite:
src/
├── modules/ # Feature modules (orders, restaurants, users, payments)
│ ├── orders/
│ │ ├── dto/ # Request/response shapes
│ │ ├── entities/ # Database models
│ │ ├── orders.module.ts
│ │ ├── orders.controller.ts
│ │ ├── orders.service.ts
│ │ └── orders.repository.ts
│ └── ...
├── common/ # Shared code across all modules
│ ├── guards/ # Auth guards (JwtGuard, RolesGuard)
│ ├── decorators/ # Custom decorators (CurrentUser, Public)
│ ├── filters/ # Exception filters
│ ├── pipes/ # Validation pipes
│ ├── interceptors/ # Logging, timing interceptors
│ └── dto/ # Shared DTOs (PaginationDto, ApiResponse)
├── config/ # Configuration module
├── database/ # Migrations, seeds, database config
│ ├── migrations/
│ └── seeds/
└── main.ts # Entry pointThe rule: If a file doesn't belong to a specific feature, it goes in common/. If it's database-related but not a model, it goes in database/.
The number one cause of confusing startup failures: a missing environment variable that only gets read when a certain endpoint is hit. By then, you've already deployed.
Fix it with a configuration module that validates all env vars at boot:
npm install @nestjs/config joi// src/config/env.config.ts
import * as Joi from 'joi';
export const envValidationSchema = Joi.object({
NODE_ENV: Joi.string().valid('development', 'production', 'test
// src/app.module.ts
import { ConfigModule } from '@nestjs/config';
import { envValidationSchema } from './config/env.config';
@Module({
imports: [
ConfigModule.forRoot({
validationSchema: envValidationSchema,
Never do this:
const dbUrl = process.env.DATABASE_URL; // undefined if missing — crashes later
process.env.API_KEY || 'default-key'; // defaults silently hide config errorsThe app starts fine, then crashes at 3 AM when traffic hits the endpoint that uses . Validate everything at boot.
Now when you forget to set DATABASE_URL, the app refuses to start with a clear error message:
[ConfigModule] Error: "DATABASE_URL" is requiredThis is what you want. Fail fast, fail early.
Kubernetes (and your deployment platform) needs two endpoints to know your app is healthy:
// src/common/controllers/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheckService, TypeOrmHealthIndicator } from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
npm install @nestjs/terminus @nestjs/axios/healthz = "Is the process running?" — always returns 200.
/readyz = "Can this pod handle traffic?" — returns 200 only if the database is reachable.
Don't make /healthz depend on the database. If the database is temporarily unavailable, you don't want Kubernetes to restart the pod — you want it to keep serving cached data while the DB recovers. /readyz handles the "should we route traffic here?" decision.
NestJS has a built-in ValidationPipe that automatically validates incoming request bodies against your DTO classes:
// src/main.ts
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
With this single line, every POST and PATCH endpoint gets automatic body validation. No manual if-checks needed.
Pick these once, enforce them in code reviews, and never break them:
| What | Convention | Example |
|---|---|---|
| Files | kebab-case | orders.controller.ts |
| Classes | PascalCase | OrdersController |
| Functions/vars | camelCase | findOne() |
| Database tables | snake_case, plural | order_items |
| Database columns | snake_case | created_at |
| Env vars | UPPER_SNAKE_CASE | DATABASE_URL |
| Git branches | kebab-case | feat/add-payment |
| Commits | Conventional commits | |
The rule: If you see a file called orderController.ts in a PR, send it back. Consistency is free — inconsistency costs hours of confusion.
.gitignore and README.md.gitignore for a NestJS project:node_modules/
dist/
.env
.env.local
*.log
coverage/README.md template:# QuickBite API
Food delivery backend built with NestJS + PostgreSQL.
## Prerequisites
- Node.js v20+
- PostgreSQL 14+
- Redis 7+
## Setup
1. Clone the repo
2. `cp .env.example .env` and fill in values
3. `npm install`
4. `npm run db:migrate`
5. `npm run start:dev`
Here's what the main.ts looks like when you've completed the checklist:
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create
| Mistake | Why it's wrong | What to do instead |
|---|---|---|
| No env validation | App starts, crashes later | Joi validation at boot |
/healthz depends on DB | K8s restarts healthy pod | /healthz = process only |
| Mixed naming conventions | Confusion in PRs | Pick one, enforce it |
| No global validation pipe | Manual if-checks in every endpoint | One line in main.ts |
| Hardcoded config values | Secrets committed to git | .env file + .env.example |
No .gitignore | node_modules in version control | Add it before first commit |
The day-1 checklist takes 15 minutes. Skipping it costs hours — and it costs them on the worst day, because you'll discover the missing DATABASE_URL at 3 AM during your first deployment, not during a calm afternoon setup.
API_KEY