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!