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!
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:
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);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 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.
1npm install @nestjs/typeorm typeorm pg
2npm install --save-dev @types/pg1// 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 {}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}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}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}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}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 {}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}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}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}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!