GUÍA PARA CONSTRUIR APIS CON NESTJS PARA PRINCIPIANTES
Introducción a NestJS y APIs RESTful
NestJS es un marco MVC para construir aplicaciones del lado del servidor con Node.js, diseñado para ser eficiente y escalable. Utiliza TypeScript de forma nativa, pero permite programar en JavaScript puro, combinando paradigmas como programación orientada a objetos, programación funcional y programación reactiva funcional. Su arquitectura predefinida facilita la creación de aplicaciones testables, escalables y mantenibles, ideal para desarrolladores que buscan estructura en proyectos backend. Este tutorial guía paso a paso en la construcción de una API RESTful de un blog con NestJS, integrando Sequelize para la base de datos Postgres, autenticación con Passport, validación de entradas y protección de rutas con JWT.
Para seguir este tutorial, es necesario tener conocimientos básicos de TypeScript y JavaScript. Experiencia con Angular es útil, pero no imprescindible, ya que se explicarán todos los conceptos necesarios. Se requiere tener instalado Node.js (versión >= 16 recomendada para 2025), Postman para probar endpoints y una base de datos Postgres configurada.
Instalación y configuración inicial
Para comenzar, instala la CLI de NestJS globalmente para facilitar la creación de proyectos. Ejecuta el siguiente comando en tu terminal:
npm i -g @nestjs/cli
Crea un nuevo proyecto y accede al directorio generado:
nest new nest-blog-api
cd nest-blog-api
npm run start:dev
Abre tu navegador en http://localhost:3000 y verifica que aparece el mensaje “Hello World”. Esto confirma que la aplicación NestJS está funcionando correctamente. La estructura inicial del proyecto será:
nest-blog-api/
├── src/
│   ├── app.module.ts
│   ├── main.ts
├── package.json
├── tsconfig.json
Si encuentras errores al ejecutar npm run start:dev, verifica que la versión de TypeScript en package.json sea compatible (por ejemplo, typescript: ^5.2.2 para 2025). Elimina node_modules y package-lock.json, luego ejecuta npm install.
Configuración de Sequelize y Postgres
Instala las dependencias necesarias para integrar Sequelize con Postgres:
npm install -g sequelize
npm install --save sequelize sequelize-typescript pg pg-hstore
npm install --save-dev @types/sequelize
npm install --save dotenv
Crea un módulo de base de datos con el comando:
nest generate module core/database
Interfaz de configuración de la base de datos
Dentro de src/core/database, crea una carpeta interfaces y un archivo dbConfig.interface.ts:
export interface IDatabaseConfigAttributes {
    username?: string;
    password?: string;
    database?: string;
    host?: string;
    port?: number | string;
    dialect?: string;
    urlDatabase?: string;
}
export interface IDatabaseConfig {
    development: IDatabaseConfigAttributes;
    test: IDatabaseConfigAttributes;
    production: IDatabaseConfigAttributes;
}
Configuración de entornos
Crea el archivo database.config.ts en src/core/database:
import * as dotenv from "dotenv";
import { IDatabaseConfig } from "./interfaces/dbConfig.interface";
dotenv.config();
export const databaseConfig: IDatabaseConfig = {
    development: {
        username: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_NAME_DEVELOPMENT,
        host: process.env.DB_HOST,
        port: process.env.DB_PORT,
        dialect: process.env.DB_DIALECT,
    },
    test: {
        username: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_NAME_TEST,
        host: process.env.DB_HOST,
        port: process.env.DB_PORT,
        dialect: process.env.DB_DIALECT,
    },
    production: {
        username: process.env.DB_USER,
        password: process.env.DB_PASS,
        database: process.env.DB_NAME_PRODUCTION,
        host: process.env.DB_HOST,
        dialect: process.env.DB_DIALECT,
    },
};
Archivo de entorno
En la raíz del proyecto, crea los archivos .env y .env.sample:
DB_HOST=localhost
DB_PORT=5432
DB_USER=tu_usuario
DB_PASS=tu_contraseña
DB_DIALECT=postgres
DB_NAME_TEST=test_db
DB_NAME_DEVELOPMENT=dev_db
DB_NAME_PRODUCTION=prod_db
JWTKEY=tu_clave_secreta
TOKEN_EXPIRATION=48h
BEARER=Bearer
Asegúrate de configurar .env con los valores correctos y añadirlo a .gitignore. Instala el paquete @nestjs/config para gestionar variables de entorno:
npm install --save @nestjs/config
Actualiza app.module.ts para cargar las variables de entorno globalmente:
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
@Module({
    imports: [ConfigModule.forRoot({ isGlobal: true })],
})
export class AppModule {}
Proveedor de base de datos
Crea database.providers.ts en src/core/database:
import { Sequelize } from "sequelize-typescript";
import { SEQUELIZE, DEVELOPMENT, TEST, PRODUCTION } from "../constants";
import { databaseConfig } from "./database.config";
export const databaseProviders = [
    {
        provide: SEQUELIZE,
        useFactory: async () => {
            let config;
            switch (process.env.NODE_ENV) {
                case DEVELOPMENT:
                    config = databaseConfig.development;
                    break;
                case TEST:
                    config = databaseConfig.test;
                    break;
                case PRODUCTION:
                    config = databaseConfig.production;
                    break;
                default:
                    config = databaseConfig.development;
            }
            const sequelize = new Sequelize(config);
            sequelize.addModels([]);
            await sequelize.sync();
            return sequelize;
        },
    },
];
Crea una carpeta constants en src/core con un archivo index.ts:
export const SEQUELIZE = "SEQUELIZE";
export const DEVELOPMENT = "development";
export const TEST = "test";
export const PRODUCTION = "production";
Actualiza database.module.ts:
import { Module } from "@nestjs/common";
import { databaseProviders } from "./database.providers";
@Module({
    providers: [...databaseProviders],
    exports: [...databaseProviders],
})
export class DatabaseModule {}
Importa el módulo en app.module.ts:
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { DatabaseModule } from "./core/database/database.module";
@Module({
    imports: [ConfigModule.forRoot({ isGlobal: true }), DatabaseModule],
})
export class AppModule {}
Prefijo global para endpoints
Configura un prefijo global api/v1 en main.ts:
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    app.setGlobalPrefix("api/v1");
    await app.listen(3000);
}
bootstrap();
Módulo de usuarios
Crea el módulo de usuarios para gestionar operaciones relacionadas con usuarios:
nest generate module modules/users
Servicio de usuarios
Genera el servicio:
nest generate service modules/users
Modelo de usuario
Crea user.entity.ts en src/modules/users:
import { Table, Column, Model, DataType } from "sequelize-typescript";
@Table
export class User extends Model<User> {
    @Column({
        type: DataType.STRING,
        allowNull: false,
    })
    name: string;
    @Column({
        type: DataType.STRING,
        unique: true,
        allowNull: false,
    })
    email: string;
    @Column({
        type: DataType.STRING,
        allowNull: false,
    })
    password: string;
    @Column({
        type: DataType.ENUM,
        values: ["male", "female"],
        allowNull: false,
    })
    gender: string;
}
DTO de usuario
Crea una carpeta dto en src/modules/users y un archivo user.dto.ts:
export class UserDto {
    readonly name: string;
    readonly email: string;
    readonly password: string;
    readonly gender: string;
}
Proveedor de usuarios
Crea users.providers.ts en src/modules/users:
import { User } from "./user.entity";
import { USER_REPOSITORY } from "../../core/constants";
export const usersProviders = [
    {
        provide: USER_REPOSITORY,
        useValue: User,
    },
];
Añade la constante USER_REPOSITORY en src/core/constants/index.ts:
export const USER_REPOSITORY = "USER_REPOSITORY";
Actualiza users.module.ts:
import { Module } from "@nestjs/common";
import { UsersService } from "./users.service";
import { usersProviders } from "./users.providers";
@Module({
    providers: [UsersService, ...usersProviders],
    exports: [UsersService],
})
export class UsersModule {}
Servicio de usuarios
Actualiza users.service.ts:
import { Injectable, Inject } from "@nestjs/common";
import { User } from "./user.entity";
import { UserDto } from "./dto/user.dto";
import { USER_REPOSITORY } from "../../core/constants";
@Injectable()
export class UsersService {
    constructor(
        @Inject(USER_REPOSITORY) private readonly userRepository: typeof User
    ) {}
    async create(user: UserDto): Promise<User> {
        return await this.userRepository.create<User>(user);
    }
    async findOneByEmail(email: string): Promise<User> {
        return await this.userRepository.findOne<User>({ where: { email } });
    }
    async findOneById(id: number): Promise<User> {
        return await this.userRepository.findOne<User>({ where: { id } });
    }
}
Añade el modelo User a database.providers.ts:
import { Sequelize } from "sequelize-typescript";
import { SEQUELIZE, DEVELOPMENT, TEST, PRODUCTION } from "../constants";
import { databaseConfig } from "./database.config";
import { User } from "../../modules/users/user.entity";
export const databaseProviders = [
    {
        provide: SEQUELIZE,
        useFactory: async () => {
            let config;
            switch (process.env.NODE_ENV) {
                case DEVELOPMENT:
                    config = databaseConfig.development;
                    break;
                case TEST:
                    config = databaseConfig.test;
                    break;
                case PRODUCTION:
                    config = databaseConfig.production;
                    break;
                default:
                    config = databaseConfig.development;
            }
            const sequelize = new Sequelize(config);
            sequelize.addModels([User]);
            await sequelize.sync();
            return sequelize;
        },
    },
];
Módulo de autenticación
Crea el módulo de autenticación:
nest generate module modules/auth
nest generate service modules/auth
nest generate controller modules/auth
Estrategia local de Passport
Instala las dependencias necesarias:
npm install --save @nestjs/passport passport passport-local
npm install --save-dev @types/passport-local
npm install --save bcrypt
Crea local.strategy.ts en src/modules/auth:
import { Strategy } from "passport-local";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { AuthService } from "./auth.service";
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
    constructor(private readonly authService: AuthService) {
        super();
    }
    async validate(username: string, password: string): Promise<any> {
        const user = await this.authService.validateUser(username, password);
        if (!user) {
            throw new UnauthorizedException("Credenciales inválidas");
        }
        return user;
    }
}
Actualiza auth.module.ts:
import { Module } from "@nestjs/common";
import { PassportModule } from "@nestjs/passport";
import { AuthService } from "./auth.service";
import { AuthController } from "./auth.controller";
import { UsersModule } from "../users/users.module";
import { LocalStrategy } from "./local.strategy";
@Module({
    imports: [PassportModule, UsersModule],
    providers: [AuthService, LocalStrategy],
    controllers: [AuthController],
})
export class AuthModule {}
Servicio de autenticación
Actualiza auth.service.ts:
import { Injectable } from "@nestjs/common";
import * as bcrypt from "bcrypt";
import { UsersService } from "../users/users.service";
@Injectable()
export class AuthService {
    constructor(private readonly userService: UsersService) {}
    async validateUser(username: string, pass: string) {
        const user = await this.userService.findOneByEmail(username);
        if (!user) {
            return null;
        }
        const match = await this.comparePassword(pass, user.password);
        if (!match) {
            return null;
        }
        const { password, ...result } = user["dataValues"];
        return result;
    }
    private async comparePassword(enteredPassword, dbPassword) {
        const match = await bcrypt.compare(enteredPassword, dbPassword);
        return match;
    }
}
Estrategia JWT
Instala las dependencias para JWT:
npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt
Crea jwt.strategy.ts en src/modules/auth:
import { ExtractJwt, Strategy } from "passport-jwt";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { UsersService } from "../users/users.service";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
    constructor(private readonly userService: UsersService) {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: process.env.JWTKEY,
        });
    }
    async validate(payload: any) {
        const user = await this.userService.findOneById(payload.id);
        if (!user) {
            throw new UnauthorizedException("No autorizado");
        }
        return payload;
    }
}
Actualiza auth.module.ts para incluir JWT:
import { Module } from "@nestjs/common";
import { PassportModule } from "@nestjs/passport";
import { JwtModule } from "@nestjs/jwt";
import { AuthService } from "./auth.service";
import { AuthController } from "./auth.controller";
import { UsersModule } from "../users/users.module";
import { LocalStrategy } from "./local.strategy";
import { JwtStrategy } from "./jwt.strategy";
@Module({
    imports: [
        PassportModule,
        UsersModule,
        JwtModule.register({
            secret: process.env.JWTKEY,
            signOptions: { expiresIn: process.env.TOKEN_EXPIRATION },
        }),
    ],
    providers: [AuthService, LocalStrategy, JwtStrategy],
    controllers: [AuthController],
})
export class AuthModule {}
Métodos adicionales en AuthService
Actualiza auth.service.ts para incluir métodos de login y signup:
import { Injectable } from "@nestjs/common";
import * as bcrypt from "bcrypt";
import { JwtService } from "@nestjs/jwt";
import { UsersService } from "../users/users.service";
@Injectable()
export class AuthService {
    constructor(
        private readonly userService: UsersService,
        private readonly jwtService: JwtService
    ) {}
    async validateUser(username: string, pass: string) {
        const user = await this.userService.findOneByEmail(username);
        if (!user) {
            return null;
        }
        const match = await this.comparePassword(pass, user.password);
        if (!match) {
            return null;
        }
        const { password, ...result } = user["dataValues"];
        return result;
    }
    public async login(user) {
        const token = await this.generateToken(user);
        return { user, token };
    }
    public async create(user) {
        const pass = await this.hashPassword(user.password);
        const newUser = await this.userService.create({
            ...user,
            password: pass,
        });
        const { password, ...result } = newUser["dataValues"];
        const token = await this.generateToken(result);
        return { user: result, token };
    }
    private async generateToken(user) {
        const token = await this.jwtService.signAsync(user);
        return token;
    }
    private async hashPassword(password) {
        const hash = await bcrypt.hash(password, 10);
        return hash;
    }
    private async comparePassword(enteredPassword, dbPassword) {
        const match = await bcrypt.compare(enteredPassword, dbPassword);
        return match;
    }
}
Controlador de autenticación
Actualiza auth.controller.ts:
import { Controller, Body, Post, UseGuards, Request } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { AuthService } from "./auth.service";
import { UserDto } from "../users/dto/user.dto";
@Controller("auth")
export class AuthController {
    constructor(private authService: AuthService) {}
    @UseGuards(AuthGuard("local"))
    @Post("login")
    async login(@Request() req) {
        return await this.authService.login(req.user);
    }
    @Post("signup")
    async signUp(@Body() user: UserDto) {
        return await this.authService.create(user);
    }
}
Validación de datos
Instala las dependencias para validación:
npm install --save class-validator class-transformer
Crea una carpeta pipes en src/core y un archivo validate.pipe.ts:
import {
    Injectable,
    ArgumentMetadata,
    BadRequestException,
    ValidationPipe,
    UnprocessableEntityException,
} from "@nestjs/common";
@Injectable()
export class ValidateInputPipe extends ValidationPipe {
    public async transform(value, metadata: ArgumentMetadata) {
        try {
            return await super.transform(value, metadata);
        } catch (e) {
            if (e instanceof BadRequestException) {
                throw new UnprocessableEntityException(
                    this.handleError(e.message.message)
                );
            }
        }
    }
    private handleError(errors) {
        return errors.map((error) => error.constraints);
    }
}
Añade el pipe globalmente en main.ts:
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ValidateInputPipe } from "./core/pipes/validate.pipe";
async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    app.setGlobalPrefix("api/v1");
    app.useGlobalPipes(new ValidateInputPipe());
    await app.listen(3000);
}
bootstrap();
Actualiza user.dto.ts para incluir validaciones:
import { IsNotEmpty, MinLength, IsEmail, IsEnum } from "class-validator";
enum Gender {
    MALE = "male",
    FEMALE = "female",
}
export class UserDto {
    @IsNotEmpty()
    readonly name: string;
    @IsNotEmpty()
    @IsEmail()
    readonly email: string;
    @IsNotEmpty()
    @MinLength(6)
    readonly password: string;
    @IsNotEmpty()
    @IsEnum(Gender, {
        message: "El género debe ser masculino o femenino",
    })
    readonly gender: Gender;
}
Guardia para usuarios únicos
Crea una carpeta guards en src/core y un archivo doesUserExist.guard.ts:
import {
    CanActivate,
    ExecutionContext,
    Injectable,
    ForbiddenException,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { UsersService } from "../../modules/users/users.service";
@Injectable()
export class DoesUserExist implements CanActivate {
    constructor(private readonly userService: UsersService) {}
    canActivate(
        context: ExecutionContext
    ): boolean | Promise<boolean> | Observable<boolean> {
        const request = context.switchToHttp().getRequest();
        return this.validateRequest(request);
    }
    async validateRequest(request) {
        const userExist = await this.userService.findOneByEmail(
            request.body.email
        );
        if (userExist) {
            throw new ForbiddenException("El email ya existe");
        }
        return true;
    }
}
Actualiza auth.controller.ts para usar la guardia:
import { Controller, Body, Post, UseGuards, Request } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { AuthService } from "./auth.service";
import { UserDto } from "../users/dto/user.dto";
import { DoesUserExist } from "../../core/guards/doesUserExist.guard";
@Controller("auth")
export class AuthController {
    constructor(private authService: AuthService) {}
    @UseGuards(AuthGuard("local"))
    @Post("login")
    async login(@Request() req) {
        return await this.authService.login(req.user);
    }
    @UseGuards(DoesUserExist)
    @Post("signup")
    async signUp(@Body() user: UserDto) {
        return await this.authService.create(user);
    }
}
Módulo de publicaciones
Crea el módulo de publicaciones:
nest generate module modules/posts
nest generate service modules/posts
nest generate controller modules/posts
Entidad de publicación
Crea post.entity.ts en src/modules/posts:
import {
    Table,
    Column,
    Model,
    DataType,
    ForeignKey,
    BelongsTo,
} from "sequelize-typescript";
import { User } from "../users/user.entity";
@Table
export class Post extends Model<Post> {
    @Column({
        type: DataType.STRING,
        allowNull: false,
    })
    title: string;
    @Column({
        type: DataType.TEXT,
        allowNull: false,
    })
    body: string;
    @ForeignKey(() => User)
    @Column({
        type: DataType.INTEGER,
        allowNull: false,
    })
    userId: number;
    @BelongsTo(() => User)
    user: User;
}
DTO de publicación
Crea una carpeta dto en src/modules/posts y un archivo post.dto.ts:
import { IsNotEmpty, MinLength } from "class-validator";
export class PostDto {
    @IsNotEmpty()
    @MinLength(4)
    readonly title: string;
    @IsNotEmpty()
    readonly body: string;
}
Proveedor de publicaciones
Crea posts.providers.ts en src/modules/posts:
import { Post } from "./post.entity";
import { POST_REPOSITORY } from "../../core/constants";
export const postsProviders = [
    {
        provide: POST_REPOSITORY,
        useValue: Post,
    },
];
Añade la constante POST_REPOSITORY en src/core/constants/index.ts:
export const POST_REPOSITORY = "POST_REPOSITORY";
Actualiza posts.module.ts:
import { Module } from "@nestjs/common";
import { PostsService } from "./posts.service";
import { PostsController } from "./posts.controller";
import { postsProviders } from "./posts.providers";
@Module({
    divers: [PostsService, ...postsProviders],
    controllers: [PostsController],
})
export class PostsModule {}
Añade el modelo Post a database.providers.ts:
import { Sequelize } from "sequelize-typescript";
import { SEQUELIZE, DEVELOPMENT, TEST, PRODUCTION } from "../constants";
import { databaseConfig } from "./database.config";
import { User } from "../../modules/users/user.entity";
import { Post } from "../../modules/posts/post.entity";
export const databaseProviders = [
    {
        provide: SEQUELIZE,
        useFactory: async () => {
            let config;
            switch (process.env.NODE_ENV) {
                case DEVELOPMENT:
                    config = databaseConfig.development;
                    break;
                case TEST:
                    config = databaseConfig.test;
                    break;
                case PRODUCTION:
                    config = databaseConfig.production;
                    break;
                default:
                    config = databaseConfig.development;
            }
            const sequelize = new Sequelize(config);
            sequelize.addModels([User, Post]);
            await sequelize.sync();
            return sequelize;
        },
    },
];
Servicio de publicaciones
Actualiza posts.service.ts:
import { Injectable, Inject } from "@nestjs/common";
import { Post } from "./post.entity";
import { PostDto } from "./dto/post.dto";
import { User } from "../users/user.entity";
import { POST_REPOSITORY } from "../../core/constants";
@Injectable()
export class PostsService {
    constructor(
        @Inject(POST_REPOSITORY) private readonly postRepository: typeof Post
    ) {}
    async create(post: PostDto, userId): Promise<Post> {
        return await this.postRepository.create<Post>({ ...post, userId });
    }
    async findAll(): Promise<Post[]> {
        return await this.postRepository.findAll<Post>({
            include: [{ model: User, attributes: { exclude: ["password"] } }],
        });
    }
    async findOne(id): Promise<Post> {
        return await this.postRepository.findOne({
            where: { id },
            include: [{ model: User, attributes: { exclude: ["password"] } }],
        });
    }
    async delete(id, userId) {
        return await this.postRepository.destroy({ where: { id, userId } });
    }
    async update(id, data, userId) {
        const [numberOfAffectedRows, [updatedPost]] =
            await this.postRepository.update(
                { ...data },
                { where: { id, userId }, returning: true }
            );
        return { numberOfAffectedRows, updatedPost };
    }
}
Controlador de publicaciones
Actualiza posts.controller.ts:
import {
    Controller,
    Get,
    Post,
    Put,
    Delete,
    Param,
    Body,
    NotFoundException,
    UseGuards,
    Request,
} from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { PostsService } from "./posts.service";
import { Post as PostEntity } from "./post.entity";
import { PostDto } from "./dto/post.dto";
@Controller("posts")
export class PostsController {
    constructor(private readonly postService: PostsService) {}
    @Get()
    async findAll() {
        return await this.postService.findAll();
    }
    @Get(":id")
    async findOne(@Param("id") id: number): Promise<PostEntity> {
        const post = await this.postService.findOne(id);
        if (!post) {
            throw new NotFoundException("Publicación no encontrada");
        }
        return post;
    }
    @UseGuards(AuthGuard("jwt"))
    @Post()
    async create(@Body() post: PostDto, @Request() req): Promise<PostEntity> {
        return await this.postService.create(post, req.user.id);
    }
    @UseGuards(AuthGuard("jwt"))
    @Put(":id")
    async update(
        @Param("id") id: number,
        @Body() post: PostDto,
        @Request() req
    ): Promise<PostEntity> {
        const { numberOfAffectedRows, updatedPost } =
            await this.postService.update(id, post, req.user.id);
        if (numberOfAffectedRows === 0) {
            throw new NotFoundException("Publicación no encontrada");
        }
        return updatedPost;
    }
    @UseGuards(AuthGuard("jwt"))
    @Delete(":id")
    async remove(@Param("id") id: number, @Request() req) {
        const deleted = await this.postService.delete(id, req.user.id);
        if (deleted === 0) {
            throw new NotFoundException("Publicación no encontrada");
        }
        return "Eliminado exitosamente";
    }
}
Conclusiones
NestJS ofrece una estructura robusta para desarrollar aplicaciones backend con Node.js, facilitando la creación de APIs RESTful escalables y mantenibles. Este tutorial cubrió la configuración de una API de blog con autenticación, validación y protección de rutas, utilizando herramientas modernas como Sequelize, Passport y JWT. Al implementar módulos, servicios y controladores, se logra una arquitectura limpia y modular, ideal para proyectos de cualquier escala. Los desarrolladores pueden extender esta base para agregar funcionalidades avanzadas, como caching o WebSockets, aprovechando la flexibilidad de NestJS. Explora la documentación oficial de NestJS para profundizar en sus capacidades y optimizar tus proyectos.