Exercices â Module 01
TypeScript & NestJS Bootstrap · 5 exercices pratiques
Types et interfaces TypeScript
Créez les types TypeScript pour un systÚme de blog : interfaces Post, Author, Category, et les types utilitaires CreatePostDto et PostSummary.
Voir la solution
interface Author {
readonly id: number;
name: string;
email: string;
bio?: string;
}
interface Category {
readonly id: number;
name: string;
slug: string;
}
interface Post {
readonly id: number;
title: string;
content: string;
slug: string;
published: boolean;
publishedAt?: Date;
author: Author;
categories: Category[];
tags: string[];
createdAt: Date;
updatedAt: Date;
}
type CreatePostDto = Omit<Post, 'id' | 'createdAt' | 'updatedAt' | 'author'> & {
authorId: number;
};
type PostSummary = Pick<Post, 'id' | 'title' | 'slug' | 'published' | 'createdAt'> & {
authorName: string;
};
Décorateur personnalisé
CrĂ©ez un dĂ©corateur de paramĂštre @IpAddress() qui extrait l'adresse IP de la requĂȘte entrante, et un dĂ©corateur @Public() pour marquer des routes comme publiques.
Voir la solution
// ip-address.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const IpAddress = createParamDecorator(
(data: unknown, ctx: ExecutionContext): string => {
const request = ctx.switchToHttp().getRequest();
return request.ip || request.headers['x-forwarded-for'] || '0.0.0.0';
},
);
// public.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
// Usage
@Get('info')
getInfo(@IpAddress() ip: string) {
return { ip, timestamp: new Date() };
}
@Get('public')
@Public()
getPublicData() {
return { message: 'Accessible sans authentification' };
}
Créer un projet NestJS complet
Générez un nouveau projet NestJS avec la CLI, ajoutez la configuration des variables d'environnement (.env + ConfigModule), et configurez la ValidationPipe globale avec whitelist et transform.
Voir la solution
nest new blog-api
cd blog-api
npm install @nestjs/config class-validator class-transformer joi
// main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
}));
app.setGlobalPrefix('api/v1');
app.enableCors({ origin: process.env.CORS_ORIGIN || '*' });
const config = app.get(ConfigService);
const port = config.get<number>('PORT', 3000);
await app.listen(port);
console.log(`Application running on: http://localhost:${port}/api/v1`);
}
bootstrap();
Classe générique CrudService
Implémentez une classe abstraite générique CrudService<T, CreateDto, UpdateDto> avec les méthodes CRUD de base. Implémentez-la dans un PostsService.
Voir la solution
abstract class CrudService<T extends { id: number }, C, U> {
protected abstract repo: Repository<T>;
findAll(): Promise<T[]> {
return this.repo.find();
}
async findOne(id: number): Promise<T> {
const entity = await this.repo.findOneBy({ id } as any);
if (!entity) throw new NotFoundException(`Entity #${id} not found`);
return entity;
}
async create(dto: C): Promise<T> {
const entity = this.repo.create(dto as any);
return this.repo.save(entity);
}
async update(id: number, dto: U): Promise<T> {
await this.findOne(id);
await this.repo.update(id, dto as any);
return this.findOne(id);
}
async remove(id: number): Promise<void> {
await this.findOne(id);
await this.repo.delete(id);
}
}
@Injectable()
export class PostsService extends CrudService<Post, CreatePostDto, UpdatePostDto> {
constructor(
@InjectRepository(Post)
protected repo: Repository<Post>,
) {
super();
}
}
Configuration typée avec factory
Créez une configuration typée avec registerAs pour séparer les configs (database, jwt, mail). Utilisez un schema Joi pour la validation. Injectez la config dans un service.
Voir la solution
// config/database.config.ts
import { registerAs } from '@nestjs/config';
export default registerAs('database', () => ({
url: process.env.DATABASE_URL,
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 5432,
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASS || 'postgres',
database: process.env.DB_NAME || 'nestjs_db',
}));
// Injection dans un service
@Injectable()
export class DatabaseService {
constructor(
@Inject(databaseConfig.KEY)
private dbConfig: ConfigType<typeof databaseConfig>,
) {}
getConnectionString(): string {
const { username, password, host, port, database } = this.dbConfig;
return `postgresql://${username}:${password}@${host}:${port}/${database}`;
}
}