A beginner-friendly NestJS application designed to teach JavaScript developers the core concepts of NestJS.
NestJS is a framework for building server-side applications with Node.js. Think of it as Express with superpowers:
- Built-in structure and organization
- TypeScript by default
- Dependency Injection (like Angular)
- Decorators for clean, readable code
npm install
```bash
# run migrations (applies all pending migrations)
# Note: the npm scripts preload ts-node so the TypeORM CLI can load TypeScript files
npm run migration:run
# revert the last migration
npm run migration:revert
# create a new blank migration file (edit it or generate with typeorm)
npm run migration:create -- NameOfYourMigration
# generate a migration based on entity changes
npm run migration:generate -- NameOfYourMigrationThe server will start at http://localhost:3000
curl http://localhost:3000/curl http://localhost:3000/hello/Alicecurl -X POST http://localhost:3000/greet \
-H "Content-Type: application/json" \
-d '{"name":"Alice","greeting":"Hi"}'Modules are containers that organize your application. Every NestJS app has at least one module.
@Module({
controllers: [AppController], // Handle HTTP requests
providers: [AppService], // Business logic
})
export class AppModule {}Why? Keeps code organized and makes it easy to separate concerns (like Users, Products, etc.)
Controllers handle incoming HTTP requests and return responses to the client.
@Controller()
export class AppController {
@Get() // Handles GET requests
getHello(): string {
return 'Hello World!';
}
}Think of it as: Express route handlers, but organized with decorators
@Get()- Handle GET requests@Post()- Handle POST requests@Param('id')- Get URL parameters@Body()- Get request body
Services contain your business logic. They keep controllers clean and focused.
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}Why separate?
- Controllers handle HTTP stuff
- Services handle business logic
- Makes code reusable and testable
Instead of creating instances manually, NestJS provides them automatically:
// ❌ The old way (manual)
const service = new AppService();
// ✅ The NestJS way (automatic)
constructor(private readonly appService: AppService) {}Benefits:
- Less boilerplate code
- Easy to test (can inject mock services)
- Automatic lifecycle management
Those @ symbols are decorators. They add functionality to classes and methods:
@Module()- Define a module@Controller()- Define a controller@Injectable()- Make a class injectable@Get(),@Post()- Define route handlers@Param(),@Body()- Extract request data
minimal-nestjs/
├── src/
│ ├── main.ts # Application entry point
│ ├── app.module.ts # Root module (wires TypeORM)
│ ├── app.controller.ts # Controller (handles HTTP)
│ ├── app.service.ts # Service (business logic)
│ ├── data-source.ts # TypeORM data source for CLI & migrations
+│ ├── entities/ # TypeORM entities
│ │ └── user.entity.ts
│ ├── migrations/ # TypeORM migrations
│ │ └── 0001-CreateUsers.ts
│ └── users/ # Users module (controller + service)
├── package.json
└── tsconfig.json
This project uses sqlite for simplicity (a single file DB). Instead of using automatic schema sync, we demonstrate how to use explicit migrations so students can learn the process of evolving a schema safely.
Files you may use or inspect:
src/data-source.ts— TypeORM DataSource used by the CLI to run migrationssrc/migrations/*— migration files (we included0001-CreateUsers.ts)db/database.sqlite— the sqlite file that will be created after running migrations (it is under.gitignoreby default in many projects; not here).
Common migration commands (worked examples):
# install deps
npm install
# run migrations (applies all pending migrations)
npm run migration:run
# revert the last migration
npm run migration:revert
# create a new blank migration file (edit it or generate with typeorm)
npm run migration:create -- NameOfYourMigration
# generate a migration based on entity changes
npm run migration:generate -- NameOfYourMigrationNote: The commands use the TypeORM CLI and the src/data-source.ts configuration.
# create a user
curl -X POST http://localhost:3000/users -H 'Content-Type: application/json' \
-d '{"name":"Alice","email":"[email protected]"}'
# list users
curl http://localhost:3000/users-
Start here: Understand the flow
main.ts→ creates appapp.module.ts→ defines structureapp.controller.ts→ handles requestsapp.service.ts→ contains logic
-
Experiment: Try modifying
- Add a new GET endpoint in
app.controller.ts - Add a new method in
app.service.ts - See how they work together!
- Add a new GET endpoint in
-
Next steps:
- Add validation (pipes)
- Connect to a database
- Create more modules (users, products, etc.)
| Express | NestJS |
|---|---|
app.get('/route', handler) |
@Get('route') decorator |
| Manual structure | Built-in structure (modules) |
| Manual dependency management | Dependency injection |
| JavaScript (usually) | TypeScript (recommended) |
| Very flexible | Opinionated (best practices) |
Use NestJS when:
- Building medium to large applications
- Working in teams (structure helps!)
- You like TypeScript and decorators
- You want built-in best practices
Stick with Express when:
- Building very simple APIs
- You prefer complete flexibility
- You're already comfortable with your Express setup
- Read the official NestJS documentation
- Try adding a new controller for a different resource (e.g., "users")
- Explore NestJS features like Guards, Pipes, and Interceptors
Happy Learning! 🚀