NestJS course Β· Module 3: TypeORM and Databases
Databases and TypeORM - the imperial treasury
In this lesson12
Welcome back to the system, young legionary! Consul Caesar.js has a new challenge for you. So far you've learned to build mighty fortifications with NestJS, but every real empire needs a place to store its tributes. It's time to learn treasury management - databases!
What is a database for the empire?
Just as the Romans stored their tributes in secure vaults in the Forum Romanum, applications need somewhere to safely store their data. A database is our digital treasury that:
- Stores tributes - application data like citizen information, orders, products
- Protects against loss - ensures durability and integrity of data
- Enables fast searching - like a treasury register showing exact locations
- Controls access - only authorized legionaries can reach for tributes
Types of databases - different kinds of vaults
Relational databases (SQL)
These are like organized warehouses with precisely labeled compartments:
1-- Legionaries table
2CREATE TABLE legionaries (
3 id SERIAL PRIMARY KEY,
4 name VARCHAR(100) NOT NULL,
5 cohort VARCHAR(100),
6 tribute_count INTEGER DEFAULT 0
7);
8
9-- Tributes table
10CREATE TABLE tributes (
11 id SERIAL PRIMARY KEY,
12 name VARCHAR(100) NOT NULL,
13 value DECIMAL(10,2),
14 legionary_id INTEGER REFERENCES legionaries(id)
15);NoSQL databases
These are like flexible bags for tributes, where you can store various things:
1// MongoDB document
2{
3 _id: ObjectId("..."),
4 name: "Marcus Aurelius",
5 cohort: "Legio X Equestris",
6 tributes: [
7 { name: "Golden Coin", value: 100 },
8 { name: "Diamond Ring", value: 500 }
9 ],
10 officers: {
11 optio: "Gaius",
12 signifer: "Marcus"
13 }
14}TypeORM - the magical treasury management system
TypeORM is a powerful tool that lets us easily manage databases in TypeScript. It's like an imperial treasury management system that automatically translates our requests into a language the database understands.
Installing TypeORM in the application
1npm install @nestjs/typeorm typeorm pg
2npm install --save-dev @types/pgConfiguring the treasury connection
1// app.module.ts
2import { Module } from '@nestjs/common';
3import { TypeOrmModule } from '@nestjs/typeorm';
4
5@Module({
6 imports: [
7 TypeOrmModule.forRoot({
8 type: 'postgres',
9 host: 'localhost',
10 port: 5432,
11 username: 'consul',
12 password: 'imperium123',
13 database: 'roman_empire',
14 entities: [__dirname + '/**/*.entity{.ts,.js}'],
15 synchronize: true, // Only during development!
16 }),
17 ],
18})
19export class AppModule {}First entity - tribute register
An entity is like a detailed register showing what a specific tribute looks like in our treasury:
1// tribute.entity.ts
2import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
3
4@Entity('tributes')
5export class Tribute {
6 @PrimaryGeneratedColumn()
7 id: number;
8
9 @Column({ length: 100 })
10 name: string;
11
12 @Column('decimal', { precision: 10, scale: 2 })
13 value: number;
14
15 @Column({ nullable: true })
16 description: string;
17
18 @Column({ default: 'unknown' })
19 location: string;
20
21 @CreateDateColumn()
22 collectedAt: Date;
23
24 @Column({ default: false })
25 isCursed: boolean;
26}TypeORM decorators - instructions for the treasurer
- @Entity() - marks this as a tribute register (table)
- @PrimaryGeneratedColumn() - unique tribute number (primary key)
- @Column() - description of a tribute property (column)
- @CreateDateColumn() - automatically records the collection date
- @UpdateDateColumn() - automatically updates the modification date
Basic operations on tributes
Creating a new tribute
1// tribute.service.ts
2import { Injectable } from '@nestjs/common';
3import { InjectRepository } from '@nestjs/typeorm';
4import { Repository } from 'typeorm';
5import { Tribute } from './tribute.entity';
6
7@Injectable()
8export class TributeService {
9 constructor(
10 @InjectRepository(Tribute)
11 private tributeRepository: Repository<Tribute>,
12 ) {}
13
14 async addTribute(tributeData: Partial<Tribute>): Promise<Tribute> {
15 const newTribute = this.tributeRepository.create(tributeData);
16 return await this.tributeRepository.save(newTribute);
17 }
18
19 async findAllTributes(): Promise<Tribute[]> {
20 return await this.tributeRepository.find();
21 }
22
23 async findTributeById(id: number): Promise<Tribute> {
24 return await this.tributeRepository.findOne({ where: { id } });
25 }
26
27 async updateTribute(id: number, updateData: Partial<Tribute>): Promise<Tribute> {
28 await this.tributeRepository.update(id, updateData);
29 return this.findTributeById(id);
30 }
31
32 async removeTribute(id: number): Promise<void> {
33 await this.tributeRepository.delete(id);
34 }
35}Column types - different kinds of tributes
1@Entity()
2export class DetailedTribute {
3 @PrimaryGeneratedColumn()
4 id: number;
5
6 // Text - tribute name
7 @Column('varchar', { length: 200 })
8 name: string;
9
10 // Integers - quantity
11 @Column('int')
12 quantity: number;
13
14 // Decimals - value
15 @Column('decimal', { precision: 10, scale: 2 })
16 value: number;
17
18 // Booleans - whether cursed
19 @Column('boolean', { default: false })
20 isCursed: boolean;
21
22 // Date - when collected
23 @Column('date')
24 collectionDate: Date;
25
26 // Long text - tribute history
27 @Column('text')
28 history: string;
29
30 // JSON - details
31 @Column('json')
32 details: {
33 material: string;
34 origin: string;
35 previousOwners: string[];
36 };
37}Data validation - checking tribute quality
1import { IsNotEmpty, IsPositive, IsOptional, Length } from 'class-validator';
2
3export class CreateTributeDto {
4 @IsNotEmpty({ message: 'Every tribute must have a name!' })
5 @Length(3, 100, { message: 'Name must be between 3 and 100 characters' })
6 name: string;
7
8 @IsPositive({ message: 'Tribute value must be greater than zero' })
9 value: number;
10
11 @IsOptional()
12 @Length(0, 500)
13 description?: string;
14
15 @IsOptional()
16 location?: string;
17}Tribute module configuration
1// tribute.module.ts
2import { Module } from '@nestjs/common';
3import { TypeOrmModule } from '@nestjs/typeorm';
4import { TributeService } from './tribute.service';
5import { TributesController } from './tributes.controller';
6import { Tribute } from './tribute.entity';
7
8@Module({
9 imports: [TypeOrmModule.forFeature([Tribute])],
10 controllers: [TributesController],
11 providers: [TributeService],
12 exports: [TributeService],
13})
14export class TributeModule {}Resource controller
1// tributes.controller.ts
2import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
3import { TributeService } from './tribute.service';
4import { CreateTributeDto } from './dto/create-tribute.dto';
5
6@Controller('tributes')
7export class TributesController {
8 constructor(private readonly tributeService: TributeService) {}
9
10 @Post()
11 async addTribute(@Body() createTributeDto: CreateTributeDto) {
12 return await this.tributeService.addTribute(createTributeDto);
13 }
14
15 @Get()
16 async getAllTributes() {
17 return await this.tributeService.findAllTributes();
18 }
19
20 @Get(':id')
21 async getTribute(@Param('id') id: string) {
22 return await this.tributeService.findTributeById(+id);
23 }
24
25 @Put(':id')
26 async updateTribute(
27 @Param('id') id: string,
28 @Body() updateData: Partial<CreateTributeDto>
29 ) {
30 return await this.tributeService.updateTribute(+id, updateData);
31 }
32
33 @Delete(':id')
34 async removeTribute(@Param('id') id: string) {
35 await this.tributeService.removeTribute(+id);
36 return { message: 'Tribute has been removed from the treasury!' };
37 }
38}Error handling - protection against curses
1import { NotFoundException, BadRequestException } from '@nestjs/common';
2
3@Injectable()
4export class TributeService {
5 //
6 async findTributeById(id: number): Promise<Tribute> {
7 if (id <= 0) {
8 throw new BadRequestException('Tribute ID must be greater than zero');
9 }
10
11 const tribute = await this.tributeRepository.findOne({ where: { id } });
12
13 if (!tribute) {
14 throw new NotFoundException(`Tribute with ID ${id} was not found in the treasury`);
15 }
16
17 return tribute;
18 }
19
20 async removeTribute(id: number): Promise<void> {
21 const tribute = await this.findTributeById(id);
22
23 if (tribute.isCursed) {
24 throw new BadRequestException('Cannot remove a cursed tribute!');
25 }
26
27 await this.tributeRepository.delete(id);
28 }
29}Practical exercise
Now that you know the basics, it's time for your own experiments! Try creating your own entity for legionaries:
1// Your code here
2@Entity('legionaries')
3export class Legionary {
4 // Add properties:
5 // - id (auto-generated)
6 // - name (text, required)
7 // - cohort (text, optional)
8 // - tributeCount (number, default 0)
9 // - joinedAt (join date)
10 // - isActive (whether active, default true)
11}Summary
Congratulations! You've learned the basics of data treasury management. Now you can:
Configure TypeORM in a NestJS application Create entities - registers for your tributes Perform basic CRUD operations on data Validate data before saving to the treasury Handle errors and protect against curses
In the next lessons you'll learn advanced techniques like table relationships, migrations, and advanced queries. Consul Caesar.js is proud of your progress!
Code for this lesson: src/database-intro.ts
1// Databases and TypeORM - Imperium Treasury
2import { Module } from '@nestjs/common';
3import { TypeOrmModule } from '@nestjs/typeorm';
4import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
5
6console.log("TypeORM - imperium treasury management system!");
7
8// ===========================================
9// 1. Basic entity (Entity) - tribute map
10// ===========================================
11
12@Entity('legionaries')
13export class Legionary {
14 @PrimaryGeneratedColumn()
15 id: number;
16
17 @Column({ length: 100 })
18 name: string;
19
20 @Column()
21 rank: string;
22
23 @Column({ default: 0 })
24 experience: number;
25
26 @Column()
27 legion: string;
28}
29
30// ===========================================
31// 2. Database connection configuration
32// ===========================================
33
34@Module({
35 imports: [
36 TypeOrmModule.forRoot({
37 type: 'postgres',
38 host: 'localhost',
39 port: 5432,
40 username: 'imperium',
41 password: 'roma_aeterna',
42 database: 'imperium_db',
43 entities: [Legionary],
44 synchronize: true, // Only in development!
45 }),
46 TypeOrmModule.forFeature([Legionary]),
47 ],
48})
49export class DatabaseModule {}
50
51// ===========================================
52// 3. Database types - treasury types
53// ===========================================
54
55// Relational (SQL):
56// - PostgreSQL - most popular, ACID, relations
57// - MySQL/MariaDB - fast, popular
58// - SQLite - lightweight, file-based
59
60// Non-relational (NoSQL):
61// - MongoDB - document-based (JSON-like)
62// - Redis - key-value, cache
63// - Cassandra - column-based, big data
64
65// ===========================================
66// 4. ORM vs raw SQL queries
67// ===========================================
68
69// ORM (Object-Relational Mapping):
70// - We write TypeScript instead of SQL
71// - Automatic mapping of objects to tables
72// - Type safety
73// - Easier migrations
74
75// Raw SQL:
76// const result = await connection.query(
77// 'SELECT * FROM legionaries WHERE rank = $1',
78// ['Centurio']
79// );
80
81// TypeORM:
82// const result = await legionaryRepo.find({
83// where: { rank: 'Centurio' }
84// });
85
86console.log("\n=== SUMMARY ===");
87console.log("TypeORM - ORM for TypeScript/JavaScript");
88console.log("@Entity - decorator defining a table");
89console.log("@Column - decorator defining a column");
90console.log("@PrimaryGeneratedColumn - primary key auto-increment");
91console.log("TypeOrmModule.forRoot() - connection configuration");
92console.log("TypeOrmModule.forFeature() - repository registration");
93Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is TypeORM?
2. What does the @Entity() decorator above a TypeScript class mean?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Code editor
Write a Legionary entity with @Entity(), @PrimaryGeneratedColumn(), @Column() name and @Column() rank
- Click in order
Arrange the elements of a TypeORM entity definition in the correct order:
- Code editor
Write a TypeOrmModule.forRoot() configuration with type: 'postgres', host, port, username, password and database